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))
}
Related
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.
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)!
I'm trying to retrieve data from an API url
This is what the implementation guide reads, so the URL should match this format:
The request for information in JSON format is submitted as a GET
operation to the endpoint:
http://digit-eyes.com/gtin/v2_0/?upc_code=x&app_key=x&signature=x&language=x&field_names=x
This is my function that fetches the data from the JSON and decodes it from JSON.
I've replaced the signature and API Key with x.
Signature is generated by combining the app_key and the barcode forming a hashed value.
func loadData() {
guard let url = URL(string: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=x&signature=x&language=en&field_names=description,brand,ingredients,image,upc_code") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, responce, error in
if let data = data {
if let decodedRepsonce = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async{
self.results = decodedRepsonce.results
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")") //This is the error I get
}.resume()
}
Here's what I get when I paste the URL into safari
I've tested the URL with: "https://itunes.apple.com/search?term=radiohead&entity=song" and it works. A noticeable difference is that this link downloads a JSON file, my URL doesn't.
I store the JSON into an array Results:
struct Result: Codable {
var description: String
var brand: String
var ingredients: String
var image: String
var upc_code: Int
}
Which is then displayed in the body:
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.indicator.padding()
List(self.results, id: \.upc_code) { item in
VStack(alignment: .leading) {
Text(item.brand)
.font(.headline)
Text(item.description)
}
}
}
EDIT
Dealing with nulls from the JSON data
To call loadData, I have an .onAppear on a VStack in the body.
.onAppear {
//let signiture = self.scannedCode.barcode.hashedValue("Ls75O8z1q9Ep9Kz0")
self.loadData(url: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=/9nOS+obsRF5&signature=DiKl4lURenoNe53I0a/i3kiAkQQ=&language=en&field_names=description,ingredients,brand,image") { error, result in
if let err = error {
print(err)
}
}
}
}
This is in a struct outside of the body
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
}
I'm now getting:
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "brand", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
In the JSON object, the brand name isn't always found, so it's sometimes null. I don't know how I can resume the decoder if a null is found.
Try this code to call in the body:
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
loadData(url: "https://google.com") { error, result in
if let err = error {
print(err)
}
}
Try to modify the struct as follows and add the other variables. I also noticed that upc_code is a String.
struct Result: Codable {
var description: String?
var brand: String?
var ingredients: String?
var image: String?
var upc_code: String?
var return_message: String?
var return_code: String?
}
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()