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)!
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’m having a really hard time fetching and parsing the following JSON. I can't even fetch the data from given url, even less parse it using my data Model "Car". Any help is more than welcomed!
JSON
{
"cars":[
{
"date_stolen":1604616183,
"description":null,
"body_colors":[
"Black",
"Blue"
],
"id":"944846",
"is_stock_img":false,
"large_img":null,
"location_found":null,
"manufacturer_name":"Toyota",
"external_id":null,
"registry_name":null,
"registry_url":null,
"serial":"36-17-01012-xl09",
"status":null,
"stolen":true,
"stolen_location":"Calgary - CA",
"thumb":null,
"title":"2017 Toyota Corolla ",
"url":"https://cars.org/944846",
"year":2017
}
]
}
struct Car: Decodable {
let cars: String
}
var cars = [Car]()
fileprivate func fetchJSON() {
let urlString = "someUrl…"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
print("DATA \n", data)
self.cars = try decoder.decode(Car.self, from: data)
print("---> ", data)
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}
You Car model does not represent you json structure.
You're looking for something like this :
struct CarsResponse: Decodable {
let cars: [CarDTO]
}
struct CarDTO: Decodable {
let id: String
let isStockImg: Bool
// And so on
}
Then just write :
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let carResponse = try decoder.decode(CarsResponse.self, from: data)
You will access your cars by writing
let cars: [CarDTO] = carResponse.cars
If you're going to use the Codable Protocol and JSONDecoder() you will need to create a Struct that matches the format of your JSON. As others have pointed out in their comments, your Car struct is completely different than the format of the data you are receiving.
If you just want to deserialize the JSON you are receiving into dictionaries containing values, you could instead use JSONSerialization.
To do that you could replace the body of your do block with code like this:
let object = try JSONSerialization.jsonObject(with: data, options: [])
You'd then have to navigate the dictionary structure of object yourself. It isn't as clean as creating a custom struct and using Codable, but it works.
Note that your JSON has syntax errors in it. Here is a cleaned-up version:
{"cars":
[
{"date_stolen":1604616183,
"description":null,
"body_colors":["Black","Blue"],
"id":944846,
"is_stock_img":false,
"large_img":null,
"location_found":null,
"manufacturer_name":"Toyota",
"external_id":null,
"registry_name":null,
"registry_url":null,
"serial":"36-17-01012-xl09",
"status":null,
"stolen":true,
"stolen_location":"Calgary - CA",
"thumb":null,
"title":"2017 Toyota Corolla ",
"url":"https://cars.org/944846",
"year":2017
}
]
}
(I added whitespace for readability. It doesn't affect the parsing one way or the other.)
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()
I was trying to parse JSON through JSONDecoder and using Alamofire to fetch the data. However, when I run the app, it shows that the data couldn't be read because of the incorrect format. I have tried many things but still did not work. Any help would be appreciated. Sources are below:
VC:
class SecondTaskVC: UIViewController {
var weatherModel = [WeatherModelDecodable]()
override func viewDidLoad() {
let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?lat=42.874722&lon=74.612222&APPID=079587841f01c6b277a82c1c7788a6c3")
Alamofire.request(url!).responseJSON { (response) in
let result = response.data
do{
let decoder = JSONDecoder()
self.weatherModel = try decoder.decode([WeatherModelDecodable].self, from: result!) // it shows this line as a problem
for weather in self.weatherModel {
print(weather.city.name)
}
}catch let error{
print("error in decoding",error.localizedDescription)
}
}
}
}
Data Model:
struct WeatherModelDecodable: Decodable {
let city: CityDecodable
}
struct CityDecodable: Decodable {
let name: String
}
Actually the response structure is different from what you are trying to do at this line,
self.weatherModel = try decoder.decode([WeatherModelDecodable].self, from: result!)
The response is not an array as you can see it in a json viewer by hitting this Url in any browser. You are expecting an array of json objects but its not. So if you decode it as a single object, it will decode properly as below,
let weatherModel = try decoder.decode(WeatherModelDecodable.self, from: result!)
print(weatherModel.city.name)
So, SecondTaskVC will look like this,
class SecondTaskVC: UIViewController {
var weatherModel: WeatherModelDecodable?
override func viewDidLoad() {
let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?lat=42.874722&lon=74.612222&APPID=079587841f01c6b277a82c1c7788a6c3")
Alamofire.request(url!).responseJSON { (response) in
let result = response.data
do{
let decoder = JSONDecoder()
self.weatherModel = try decoder.decode(WeatherModelDecodable.self, from: result!)
print(self.weatherModel!.city.name)
}catch let error{
print("error in decoding",error.localizedDescription)
}
}
}
}
You should decode the respective objects with the same structure you are getting in the response.
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))
}