I've read lots of SO question on it, but they are not applying in my case.
I follow the steps mentioned in Parse JSON from file and URL with Swift, project structure is:
The code:
import Foundation
struct DemoData: Codable {
let title: String
let description: String
}
func readLocalFile(forName name: String) -> Data? {
do {
if let bundlePath = Bundle.main.path(forResource: name,
ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
return jsonData
}
} catch {
print(error)
}
return nil
}
func parse(jsonData: Data) {
do {
let decodedData = try JSONDecoder().decode(DemoData.self,
from: jsonData)
print("Title: ", decodedData.title)
print("Description: ", decodedData.description)
print("===================================")
} catch {
print("decode error")
}
}
if let localData = readLocalFile(forName: "data") {
print("running")
parse(jsonData: localData)
} else {
print("final nil")
}
And it turn out to be:
final nil
Program ended with exit code: 0
PS: Config of the json data
You are suppressing some potential errors, I recommend this (generic) version, it throws all possible errors
enum ReadError : Error { case fileIsMissing }
func readLocalFile<T : Decodable>(forName name: String) throws -> T {
guard let url = Bundle.main.url(forResource: name,
withExtension: "json") else {
throw ReadError.fileIsMissing
}
let jsonData = try Data(contentsOf: url)
return try JSONDecoder().decode(T.self, from: jsonData)
}
do {
let localData : DemoData = try readLocalFile(forName: "data")
print("===================================")
print("running")
print("Title: ", localData.title)
print("Description: ", localData.description)
print("===================================")
} catch {
print("An error occured", error)
}
Edit:
Your code doesn't work because your target is a command line tool.
CLIs don't have a bundle therefore there is no Resources folder either.
The problem might be with the text encoding format of the json file. Please check and it should be Unicode (UTF-8) since you're using text encoding format as .utf8 in readLocalFile file method.
Related
I am new to swift programming and Xcode and am try to call mysql data from the database to Xcode using Json encoding. I was able to successfully call all the data (array) but when I decide to call only one value(column) say Courses.name I get the "Decoding Error -- Expected to decode Dictionary but found an array instead." How do I work my way around this problem? My goal is to print only courses.name
import UIKit
struct Course: Decodable {
let id: String
let name: String
let member: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "http://oriri.ng/aapl/service.php"
guard let url = URL(string: jsonUrlString) else
{ return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else{ return }
do {
let courses = try JSONDecoder().decode(Course.self, from: data)
print(courses.name)
} catch let jsonErr {
print("Error serializing json:", jsonErr)
}
}.resume()
}
}
[{"id":"1","name":"sobande_ibukun","member":"blue"}]
The [] around denotes that it is an array. Decode with the following and it should work:
let courses = try JSONDecoder().decode([Course].self, from: data)
If you are sure that it will always be one course you can do:
print(courses.first!.name)
If there may be many courses you can print every name:
courses.forEach { course in print(course.name) }
Just wanted to add to Fabians response above.
The fix for showing all of them is to have a struct with each of your keys (in the array) like so:
struct Advisor_Info: Decodable {
let advisor_email: String
let advisor_firstname: String
let advisor_lastname: String
let advisor_phonenumber: String
}
Then extract your items from the JSON dictionary string like so:
do {
let decodedResponse = try JSONDecoder().decode([Advisor_Info].self, from: data)
print(decodedResponse as Any)
for each in decodedResponse {
print(each)
print(each.advisor_email)
}
} catch DecodingError.dataCorrupted(let context) {
print(context)
} catch DecodingError.keyNotFound(let key, let context) {
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.valueNotFound(let value, let context) {
print("Value '\(value)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.typeMismatch(let type, let context) {
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("error: ", error)
}
}
Problem
I'm currently getting JSON from a server that I don't have access to. The JSON I sometimes get will put this character \u0000 at the end of a String. As a result my decoding fails because this character just fails it.
I'm trying to debug this in Playground but I keep getting this error.
Expected hexadecimal code in braces after unicode escape
Here is some sample code to try out.
import UIKit
import Foundation
struct GroceryProduct: Codable {
var name: String
}
let json = """
{
"name": "Durian \u0000"
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let product = try decoder.decode(GroceryProduct.self, from: json)
print(product.name)
Question
How do you deal with \u0000 from JSON? I have been looking at DataDecodingStrategy from the Apple documentation but I can't even test anything out because the Playground fails to even run.
Any direction or advice would be appreciated.
Update
Here is some more setup code to tryout in your Playground or a real app.
JSON test.json
{
"name": "Durian \u0000"
}
Code
extension Bundle {
func decode<T: Decodable>(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .deferredToDate
decoder.keyDecodingStrategy = .useDefaultKeys
do {
return try decoder.decode(T.self, from: data)
} catch DecodingError.keyNotFound(let key, let context) {
fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
} catch DecodingError.typeMismatch(_, let context) {
fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
} catch DecodingError.dataCorrupted(_) {
fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
} catch {
fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
}
}
}
struct GroceryProduct: Codable {
var name: String
}
// Try running this and it won't work
let results = Bundle.main.decode(GroceryProduct.self, from: "test.json")
print(results.name)
You need to escape \u0000 characters first - this can be done before decoding:
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(file) from bundle.")
}
let escaped = Data(String(data: data, encoding: .utf8)!.replacingOccurrences(of: "\0", with: "").utf8)
...
return try decoder.decode(T.self, from: escaped)
Note: force-unwrapping for simplicity only.
In Playground you can escape it with an additional \ (to make it work):
let json = """
{
"name": "Durian \\u0000"
}
""".data(using: .utf8)!
or replace it with \0 (to make it fail - behave like during the decoding):
let json = """
{
"name": "Durian \0"
}
""".data(using: .utf8)!
In browser my url gives results in perfect JSON format as follows
"articles": [
{
"source": {
"id": "the-times-of-india",
"name": "The Times of India"
},
"author": "Times Of India",
But Where as in Xcode output the response I am getting is as follows. How to convert this response into perfect json format
{
articles = (
{
author = "Times Of India";
content = "Hyderabad: Senior Police officials arrive at the site of the encounter. All four accused in the rape
description = "India News: All four accused in the rape and murder of woman veterinarian in Telangana have been killed in an encounter with the police. Cops claimed they tried t";
publishedAt = "2019-12-06T04:15:00Z";
source = {
name = "The Times of India";
};
},
I am using the following code to decode the json data
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let dataResponse = data, error == nil else {
print(error?.localizedDescription ?? "Response Error")
return
}
do{
//here dataResponse received from a network request
let jsonResponse = try JSONSerialization.jsonObject(with: dataResponse, options: [])
print(jsonResponse) //Response result
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
Please help me with this issue.
first you must create a Decodable struct and put it before viewController class:
struct YourArrray: Decodable {
let author: String?
let content: String?
let location: String?
let description : String?
let publishedAt : String?
let name: String?
}
declare your Url:
let jsonUrlString = "https://yourUrljson"
after that create your struct array var:
var myVar = [YourArrray]()
now you can procede to decode json:
fileprivate func fetchJsonObject() {
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, respons, err) in
guard let data = data else { return }
do {
let jsonResponse = try JSONDecoder().decode([myVar].self, from: data)
print(jsonResponse)
} catch let jsonErr {
print("Error serializing:", jsonErr)
}
}.resume()
}
Now you can simply call the function fetchJsonObject() and you're done
Hope this help :)
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
Im am decoding a JSON struct and if it fails to decode, at this point in my error checking it means that one of the fields is missing from the server response, which I want to display to the user.
When decoding this struct:
struct UserResponseObject: Decodable {
let message: String
let data: User
}
here
do {
let responseObject = try createDecoder().decode(UserResponseObject.self, from: jsonData)
//print("RESPONSE MESSAGE: ", responseObject.message)
//print("GET USER DATA: ",responseObject.data)
completion!(.success(responseObject.data))
} catch let error as NSError {
print("failure to decode user from JSON")
completion!(.failure(error))
}
if there is no field .data, I want to return the message in responseObject.message in the catch block. But I am not allowed to redecode the response into this struct.
struct ErrorObject: Decodable {
let message: String
}
How should I go about trying to get the message when the first decode fails. Thanks
You can understand the exact nature of error by adding more catch blocks:
do {
let messages = try JSONDecoder().decode(Results.self, from: data)
} catch DecodingError.dataCorrupted(let context) {
print(context)
} catch DecodingError.keyNotFound(let key, let context) {
print("Key '\(key)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.valueNotFound(let value, let context) {
print("Value '\(value)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch DecodingError.typeMismatch(let type, let context) {
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("error: ", error)
}
If your struct implemnts codable then you better use JSONEncoder & JSONDecoder
struct Language: Codable {
var name: String
var version: Int
}
let swift = Language(name: "Swift", version: 4)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(swift) {
// save `encoded` somewhere
}
if let encoded = try? encoder.encode(swift) {
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
let decoder = JSONDecoder()
if let decoded = try? decoder.decode(Language.self, from: encoded) {
print(decoded.name)
}
If any field is missing from the json, you should make it optional first of all. In your case it should be,
struct UserResponseObject: Decodable {
let message: String? // You should decide, should it be optional or not
let data: User? // You should decide, should it be optional or not
}
Also you should handle the no data case in your do block...
As a result in your try-catch block try to create a new instance of UserResponseObject object;
do {
let responseObject = try createDecoder().decode(UserResponseObject.self, from: jsonData)
//print("RESPONSE MESSAGE: ", responseObject.message)
//print("GET USER DATA: ",responseObject.data)
if(responseObject.data) {
completion!(.success(responseObject.data))
}
else {
completion!(.failure(//no data error handler))
}
} catch let error as NSError {
let responseObject = UserResponseObject(message: error.localizedDescription, data: nil)
print("failure to decode user from JSON")
completion!(.failure(error))
}