Decode nested JSON arrays and dictionaries in Swift using Structs with JSONserialization - ios

I am trying to create some structs to decode some JSON received from an API using JSONSerialization.jsonObject(with: data, options: [])
This is what the JSON looks like:
{"books":[{"title":"The Fountainhead.","author":"Ayn Ranyd"},{"title":"Tom Sawyer","author":"Mark Twain"},{"title":"Warhol","author":"Blake Gopnik"}]}
Here are the structs that I am trying to use for decoding.
struct BooksReturned : Codable {
let books : [Book]?
}
struct Book : Codable {
let BookParts: Array<Any>?
}
struct BookParts : Codable {
let titleDict : Dictionary<String>?
let authorDict : Dictionary<String>?
}
The error is:
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.})))
The non-working code I am using to decode is:
let task = session.dataTask(with: url) { data, response, error in
if let data = data, error == nil {
let nsdata = NSData(data: data)
DispatchQueue.main.async {
if let str = String(data: data, encoding: .utf8) {
let json = try? JSONSerialization.jsonObject(with: data, options: [])
do {
let mybooks = try JSONDecoder().decode(BooksReturned.self, from: data)
//do something with book
}
} catch {
print(error.localizedDescription)
print(error)
}
}
}
} else {
// Failure
}
}
task.resume()
}
I have some very limited ability to change JSON. The only thing I can do is remove the "books" : Everything else is received from an external API.
Thanks for any suggestions on how to get this to work.

The JSON you provided seems to be valid. Modify your Book model and the decoding part as the following.
Model:
struct Book: Codable {
let title, author: String
}
Decoding:
let task = session.dataTask(with: url) { data, response, error in
if let data = data, error == nil {
DispatchQueue.main.async {
do {
let mybooks = try JSONDecoder().decode(BooksReturned.self, from: data)
print(mybooks)
}
} catch {
print(error.localizedDescription)
print(error)
}
}
} else {
// Failure
}
task.resume()

Related

The data couldn't be read because it isn't in the correct format

Hello I am creating an app with Xcode and I am having the following problem, I created this API with mockapi.io (if you enter the link you'll see the JSON data) https://62858a2ff0e8f0bb7c057f14.mockapi.io/categorias
If you dont want to enter the link here is how it looks the JSON: (By default the JSON has an array without name as the root and that can't be modified)
[
{
"categorie":"Fruits",
"id":"1"
},
{
"categorie":"Animals",
"id":"2"
},
{
"categorie":"Vegetables",
"id":"3"
},
{
"categorie":"Juices",
"id":"4"
},
{
"categorie":"Alcohol",
"id":"5"
},
{
"categorie":"Desserts",
"id":"6"
}
]
The problem I have is that when I try to decode the data from the API it cant't be readed because is in the wrong format, I am trying to recreate the same code of this youtube video, but with my API: https://www.youtube.com/watch?v=sqo844saoC4
This is how my code looks like:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://62858a2ff0e8f0bb7c057f14.mockapi.io/categorias"
getData(from: url)
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("failed to convert\(error.localizedDescription)")
}
guard let json = result else {
return
}
print(json.items.categorie) // 👈 HERE ES WHERE THE PRINT HAPPENS
})
task.resume()
}
}
// 👇 I THINK THE PROBLEM IS DEFINITELY HERE
struct Response: Codable {
let items: ResultItem
}
struct ResultItem: Codable {
let categorie: String
}
When I execute this the terminal print: "The data couldn't be read becouse it isn't in the correct format."
I am pretty sure the error comes of the way I am calling the data in the structs, so my question is...? How can I exactly call the data from my API's JSON in the code?
yes ,there is an issue in your model you don't need to use the (Response) only use the Model (ResultItem) the JSON isn't complex JSON like that it just array of (ResultItem)
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
do {
let result = try JSONDecoder().decode([ResultItem].self, from: data)
print(result)
}
catch {
print("failed to convert\(error.localizedDescription)")
}
})
task.resume()
}
struct ResultItem: Codable {
let categorie: String
}
The response you get is an array of ResultItems rather than a single object, so you need to decode it as an array:
result = try JSONDecoder().decode(Array<ResultItem>.self, from: data)
That said, you won't need the Response struct at all and the type of result will be [ResultItem].

SWIFT - JSON error: The data couldn’t be read because it isn’t in the correct format

How to correct this error: JSON error: The data couldn’t be read because it isn’t in the correct format?
struct LanguageText: Decodable {
let id_language: Int
let language_text: String
}
func textLoad() {
let switchcase = "loginWords"
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "switchcase=\(switchcase)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
return // check for fundamental networking error
}
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
This is the JSON format:
[{"id_language":"15","language_text":"Female"},
{"id_language":"16","language_text":"Male"},
{"id_language":"17","language_text":"Other"},
{"id_language":"1000","language_text":"Hello there!"}]
Thanks!
You are trying to put id_language into a Int-Value, but in your JSON id_language is String.
Change id_language to String
struct LanguageText: Decodable {
let id_language: String
let language_text: String
}
Or you have to edit your JSON-File
[{"id_language":15,"language_text":"Female"},
{"id_language":16,"language_text":"Male"},
{"id_language":17,"language_text":"Other"},
{"id_language":1000,"language_text":"Hello there!"}]
For parsing JSON I can recommend this site
In your model you could do something like this:
struct LanguageText: Decodable {
let languageId: String
let languageText: String
enum CodingKeys: String, CodingKey {
case languageId = "id_language"
case languageText = "language_text"
}
}
In your do catch do the data parse:
do {
let result = try JSONDecoder().decode([LanguageText].self, from: data)
} catch {
print("JSON error: \(error.localizedDescription)")
}
Use this to get array from row data.
let dataArray = getArrayFromJsonString(rowData: data)
func getArrayFromJsonString(arrayString:String)-> [[String : Any]] {
do {
return try JSONSerialization.jsonObject(with:
arrayString.data(using:
String.Encoding.utf8, allowLossyConversion: false)!,
options:
JSONSerialization.ReadingOptions.allowFragments) as! [[String :
Any]]
} catch let error {
print("Error: \(error)")
return []
}
}

How to parse JSON using init()

I can't display the json Array by using its object
showing this error :
"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value"
class sample{
var jarray:[[String:Any]]!
init(url: String) {
let urll = URL(string: url)
var request = URLRequest(url: urll!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request, completionHandler: {(Data,response,Error) in
do
{
let jsonresponse = try JSONSerialization.jsonObject(with: Data!, options: [])
let jsonarray = jsonresponse as? [[String:Any]]
self.jarray = jsonarray!
print(self.jarray)
DispatchQueue.main.async {
}
}
catch let parsingerror
{
print("error",parsingerror)
}
})
task.resume()
}
}
First of all: Handle always errors and unwrap optionals safely.
Second of all Data and Error (capitalized) are reserved words, use always lowercased parameter labels in closures (and uppercased class names).
Many lines in your code are redundant.
class Sample {
var jarray = [[String:Any]]()
init(url: String) {
guard let urll = URL(string: url) else { return }
let task = URLSession.shared.dataTask(with: urll) { data, _ , error in
if let error = error { print(error); return }
do
{
// if error is nil then data is guaranteed to be non-nil
if let jsonarray = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] {
self.jarray = jsonarray
print(self.jarray)
DispatchQueue.main.async {
}
}
}
catch {
print("error", error)
}
}
task.resume()
}
}
Note: It's bad practice to run asynchronous tasks in init methods
Avoid using force unwrapping unnecessarily. I might result in unwanted crashes in your app. In your code,
Check if Data is nil. If it is, the below line will result in runtime exception.
let jsonresponse = try JSONSerialization.jsonObject(with: Data!, options: [])
In the below line of code, check whether jsonarray is nil.
self.jarray = jsonarray!
If not, then add the line where your app is crashing.
Try replacing your code with:
class sample {
var jarray: [[String:Any]]?
init(url: String) {
if let urll = URL(string: url) {
URLSession.shared.dataTask(with: urll) { (data, response, error) in
do {
if let data = data {
let jsonresponse = try JSONSerialization.jsonObject(with: data, options: [])
self.jarray = jsonresponse as? [[String:Any]]
print(self.jarray)
DispatchQueue.main.async {
}
}
} catch {
print("error",error)
}
}.resume()
}
}
}
Also, don't use reserved words as variable names like you did for Data and Error.
Most importantly - Never use forced unwrapping (!) with server response. API response might not be as expected always. Try handling that.
JSONSerialization is now old-fashioned way. Apple introduced Codable protocol that handles for you serialisation and deserialisation of objects.
Example:
struct Photo: Codable
{
//String, URL, Bool and Date conform to Codable.
var title: String
var url: URL
var isSample: Bool
//The Dictionary is of type [String:String] and String already conforms to Codable.
var metaData: [String:String]
//PhotoType and Size are also Codable types
var type: PhotoType
var size: Size
}
And in the response from the server:
if let jsonData = jsonString.data(using: .utf8)
{
let photoObject = try? JSONDecoder().decode(Photo.self, from: jsonData)
}

Parsing JSON response using Codable gives error in swift

I'm trying to parse JSON response using Codable but it gives me error.
I tried to refer from the below stack overflow link but did not work.
how do I parse Any in dictionary using swift
Below is my code, not sure where I'm wrong in this.
> enum JSONError: String,Error {
> case NoData = "ERROR: no data"
> case ConversionFailed = "ERROR: conversion from JSON failed"
> }
struct Owner : Decodable {
let full_name : String
let html_url:String
let follower:follower
}
struct follower : Decodable {
let followers_url : String
}
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
guard let data = data else {
throw JSONError.NoData
}
let jsonResponse = try JSONSerialization.jsonObject(with:
data)
let entries = try! JSONDecoder().decode([Owner].self, from: jsonResponse as! Data)
print(jsonResponse)
} catch let error as JSONError {
print(error.rawValue)
} catch let error as NSError {
print(error.debugDescription)
}
}.resume()
}
I need to get 3 information from this response - full name, html url and followers.
Link for web api
https://api.github.com/search/repositories?q=language:ruby
Please latest have a look at the code.
Below is the error message :
'__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580). 2019-02-09
16:17:42.062971+0530 PhotoViewwer[13342:259997] Could not cast value
of type '__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580).
Thanks
Please learn to read JSON. It's pretty easy. There are only two collection types, array ([]) and dictionary ({})
Your structs are wrong.
In the root dictionary of the JSON there is an array of dictionaries for key items.
In each dictionary there are keys full_name and owner (here is the location of the Owner struct).
A dictionary for key follower does not exist.
These structs represent the JSON correctly
struct Response : Decodable {
let items : [Item]
}
struct Item : Decodable {
let fullName : String
let owner : Owner
}
struct Owner : Decodable {
let htmlUrl : URL // URL strings can be decoded directly into URL
let followersUrl : URL
}
Add a completion handler to your function and use an enum as result type. The failure case returns all real errors. An URLRequest is redundant. Just pass the URL. The JSONSerialization line is pointless.
The convertFromSnakeCase strategy converts snake_cased keys to camelCased struct members
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let entries = try decoder.decode(Response.self, from: data!)
completion(.success(entries))
} catch {
completion(.failure(error))
}
}.resume()
}
And call it
jsonParser { result in
switch result {
case .success(let entries) : print(entries)
case .failure(let error) : print(error)
}
}
Basically never use NS... classes if there are native equivalents, here URL for NSURL and URLRequest for NS(Mutable)URLRequest
Edit:
In Swift 5 the syntax becomes more convenient using the native Result type. It is able to convert the throwing expression
enum Result {
case success(Response), failure(Error)
}
func jsonParser(completion: #escaping (Result<Response,Error>) -> Void) {
let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
if let error = error { completion(.failure(error)); return }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
completion(Result{ try decoder.decode(Response.self, from: data!) })
}.resume()
}
You need
func jsonParser() {
let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
guard let endpoint = NSURL(string: urlPath) else {
print("Error creating endpoint")
return
}
let request = NSMutableURLRequest(url:endpoint as URL)
URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
do {
let dec = JSONDecoder()
dec.keyDecodingStrategy = .convertFromSnakeCase
let entries = try dec.decode(Root.self, from:data!)
print(entries)
} catch {
print(error)
}
}.resume()
}
struct Root : Decodable {
let items:[Item]
}
struct Owner: Codable {
let login: String
let id: Int
let nodeId: String
let avatarUrl: String
let gravatarId: String
let url, htmlUrl, followersUrl: String
let followingUrl, gistsUrl, starredUrl: String
let subscriptionsUrl, organizationsUrl, reposUrl: String
let eventsUrl: String
}
struct Item: Codable {
let fullName : String
let htmlUrl:String
let owner: Owner
}
You shouldn't cast the response to data here
from: jsonResponse as! Data)
as it will crash

How to parse JSON when there is no key but only an Integer / String value?

How can I parse this JSON file ? My code is working when both keys and values are available .
My code so far :
let url = URL(string: "http://uhunt.felix-halim.net/api/uname2uid/felix_halim")
let task = URLSession.shared.dataTask(with: url!, completionHandler: {
(data, response, error) in
print("Task Started")
if error != nil {
print("In Error!")
} else {
if let content = data {
do {
let myJSON =
try JSONSerialization.jsonObject(with: content, options: .mutableContainers) as AnyObject
print(myJSON)
} catch {
print("In Catch!")
}
}
}
})
task.resume()
print("Finished")
If the root object of the JSON is not a dictionary or array you have to pass .allowFragments as option (btw. never pass .mutableContainers, it's meaningless in Swift)
let url = URL(string: "http://uhunt.felix-halim.net/api/uname2uid/felix_halim")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("Task Started")
guard error == nil else {
print("In Error!", error!)
return
}
do {
if let myJSON = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? Int {
print(myJSON)
}
} catch {
print("In Catch!", error)
}
}
task.resume()
print("Finished")
THIS ANSWER IS NOT CORRECT. IT IS POSSIBLE TO PARSE Int , etc like in vadian post
This is not a json object format specification.
JSON data must start with "{" for object
or "[" for array of elements.
http://www.json.org/
So, if you have got different formats I would suggest this:
Check the first letter. if "{" parse as object.
Check the first letter. if "[" parse as array.
Otherwise:
Just convert the String into Int something like this:
var num = Int("339")
If not use simple String.

Resources