How to decode NaN value from JSON using Swift? - json

Null values decoding works well with Codable protocol, but when I have JSON that has NaN, everything crashes, how do I solve this?
I have spent the last couple of days but did not find a solution.
Say, we have the following code:
[{
"id": 1
"apples": 193,
"oranges": NaN,
"bananas": null,
"pineapples": 405,
"watermelons": 13
"comment": "oranges and bananas have invalid values"
}]
And this struct:
struct Fruits: Codable, Identifiable {
var id: Int
var apples: Int?
var oranges: Int?
var bananas: Int?
var pineapples: Int?
var watermelons: Int?
var comment: String?
}
How to decode this with no crashes?

since your data is not valid JSON due to NaN (null is ok), you could try this approach where the original data is made into valid json, works very well for me.
Note: you are also missing a comma after id and watermelons
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear{
let json = """
[
{
"id": 1,
"apples": 193,
"oranges": NaN,
"bananas": null,
"pineapples": 405,
"watermelons": 13,
"comment": "oranges and bananas have invalid values"
}
]
"""
// simulated api data
let data = json.data(using: .utf8)!
// convert to string
let jsString = String(data: data, encoding: .utf8)!
// convert back to data after replacements
let newData = jsString.replacingOccurrences(of: "NaN", with: "null").data(using: .utf8)!
do {
let fruits = try JSONDecoder().decode([Fruits].self, from: newData)
print("\n---> fruits: \(fruits)")
} catch (let error) {
print("\n---> error: \(error)")
}
}
}
}
EDIT-1: alternative using JSContext:
import JavaScriptCore
struct ContentView: View {
var body: some View {
Text("using JSContext")
.onAppear{
let json = """
[
{
"id": 1,
"apples": 193,
"oranges": NaN,
"bananas": null,
"pineapples": 405,
"watermelons": 13,
"comment": "oranges and bananas have invalid values"
}
]
"""
let fruits = decodeToFruits(json)
print("\n---> fruits: \(fruits)")
}
}
func decodeToFruits(_ badJson: String) -> [Fruits] {
if let goodJson = JSContext().evaluateScript("JSON.stringify(\(badJson))"),
let goodStr = goodJson.toString(),
let data = goodStr.data(using: .utf8) {
do {
return try JSONDecoder().decode([Fruits].self, from: data)
} catch (let error) {
print("\n---> error: \(error)")
}
}
return []
}
}

Related

Decode JSON Array with no Attribute Name

I have looked through other threads regarding trying to parse JSON data where a JSON array has no name. From what I have found you need to use a unkeyedContainer but I'm not entirely sure from the examples I have seen how this works with the data model.
Below is a snippet of data from open charge api:
[
{
"IsRecentlyVerified": false,
"ID": 136888,
"UUID": "254B0B07-E7FC-4B4B-A37C-899BCB9D7261",
"DataProviderID": 18,
"DataProvidersReference": "0a9fdbb17feb6ccb7ec405cfb85222c4",
"OperatorID": 3,
"UsageTypeID": 1,
"AddressInfo": {
"ID": 137234,
"Title": "Ballee Road Park & Share",
"AddressLine1": "Ballee Road",
"Town": "Ballymena",
"Postcode": "BT42 2HD",
"CountryID": 1,
"Latitude": 54.844648,
"Longitude": -6.273606,
"AccessComments": "Ballee Road Park and Share, Ballymena",
"RelatedURL": "http://pod-point.com",
"Distance": 3.81818421833416,
"DistanceUnit": 2
},
"Connections": [
{
"ID": 191571,
"ConnectionTypeID": 25,
"Reference": "1",
"StatusTypeID": 50,
"LevelID": 2,
"Amps": 32,
"Voltage": 400,
"PowerKW": 22,
"CurrentTypeID": 20
},
It looks to me that the first [ and { have no attribute names which I belive is creating the error in xcode: "Error!: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))"
Here is my data model:
import Foundation
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
Here is my code:
//Find public chargers from local coordinates
func findPublicChargers(lat: Double, long: Double) {
//Use apiurl to pull all charge points that are currently in that area by adding lat and long into the api call &latitude=***&longitude=*****
let apiurl = "https://api.openchargemap.io/v3/poi/?output=json&countrycode=UK&maxresults=100&compact=true&verbose=false"
let urlString = "\(apiurl)&latitude=\(lat)&longitude=\(long)"
//print(urlString)
performRequest(urlString: urlString)
}
//Perform API Request - (London App Brewry code)
//Create the custom url
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
//print("Called")
//Create a URL Session
let session = URLSession(configuration: .default)
//Give the session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
//let dataString = String(data: safeData, encoding: .utf8)
//print(dataString)
self.parseJSON(data: safeData)
print("Data: \(safeData)")
}
}
//Start the task
task.resume()
}
}
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(PublicCharger.self, from: data)
print("Data: \(decodedData.AddressInfo[0].Title)")
} catch {
print("Error!: \(error)")
}
}
struct AddressInfo: Decodable {
let Title: String
}
I have seen that in the data model you would need to include an unkeyed container element. I'm just not sure how this should be carried out in the data model. Any light on this would be much appreciated.
Try to change your PublicCharger data model to
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
And your parseJSON function to
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([PublicCharger].self, from: data)
if !decodedData.isEmpty {
print("Data: \(decodedData[0].AddressInfo[0].Title)")
} else {
print("Empty result!")
}
} catch {
print("Error!: \(error)")
}
}

Response Serialization error, how to fix it?

I am trying to decode a json data, but it throws an error:
[] nw_protocol_get_quic_image_block_invoke dlopen libquic failed responseSerializationFailed(reason: Alamofire.AFError.ResponseSerializationFailureReason.decodingFailed(error: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))))
I think, I did it in a correct way, I checked for the type mismatch also, looks correct, but could not comprehend why am i still getting the error. .
here is how my json data response looks like:
[
{
"id": 1,
"name": "BlackJet",
"type": "Silk",
"quantity": "100"
},
[
{
"id": 2,
"name": "Toto",
"type": "Leather",
"quantity": "10"
},
...,
]
here is my struct data:
import Foundation
struct Response<T: Codable>: Codable {
var data: [T]?
}
struct MyData: Codable {
var id: Int
var name: String
var type: String
var quantity: Int
}
and my serverCommunicator func:
static func getData() -> Promise<Response<MyData>> {
let decoder = JSONDecoder()
return Promise { seal in
AF.request(API.getData, method: .get, parameters: .none).responseDecodable(of: Response<MyData>.self, decoder: decoder) { response in
switch response.result {
case .success(let val):
return seal.fulfill(val)
case .failure(let err):
return seal.reject(err)
}
}
}
}
and my apiCall func inside VC didload:
func getData() {
ServerCommunicator.getData().done { response -> Void in
guard response.data != nil, let data = response.data else {
print("Data could not be obtained.. .")
return
}
self.data = data
}.catch { (err) in
print(err)
}
}
NOTE: No api headers or parametrs exist in my api//
First you need to change the type of quantity from Int to String as per the response,
struct MyData: Codable {
var id: Int
var name: String
var type: String
var quantity: String
}
Then you will need to change the signature of getData method as below,
static func getData() -> Promise<[MyData]> {
let decoder = JSONDecoder()
return Promise { seal in
AF.request(API.getData, method: .get, parameters: .none).responseDecodable(of: [MyData].self, decoder: decoder) { response in
switch response.result {
case .success(let val):
return seal.fulfill(val)
case .failure(let err):
return seal.reject(err)
}
}
}
}

Array in JSON not parsing with custom decoding function in Swift

This question might seem similar to other questions with the same title. However, my decodable is in a function called load.
Here is what my JSON file looks like:
[
{
"id": 1001
"name": "tempName"
"item1": [
{
"id": 1101
"element": "tempElement"
},
{
"id": 1201
"element": "tempElement2"
},
]
}
]
I've built a struct temp to access the information within my JSON file after decoding it. I am not sure if it is extremely relevant but this is what it looks like:
struct Temp: Hashable, Codable, Identifiable {
var id: Int
var name: String
var item1: ExampleItem
}
struct ExampleItem: Hashable, Codable, Identifiable{
var id: Int
var element: String
}
My structs don't seem too crazy so I assume the problem occurs when I am parsing and decoding my JSON. This is the code I am using
let tempData: [Temp] = load("tempData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
I think Expected to decode Dictionary<String, Any> but found an array instead bug is occurring when the code tries to decode item1 because item1 is an array. What should I change in my load function?
First of all your JSON is invalid. It should be comma-separated:
[
{
"id": 1001,
"name": "tempName",
"item1": [
{
"id": 1101,
"element": "tempElement"
},
{
"id": 1201,
"element": "tempElement2"
},
]
}
]
Then you need to parse item1 as an array:
struct Temp: Hashable, Codable, Identifiable {
var id: Int
var name: String
var item1: [ExampleItem] // <- declare as an array
}
Note: I suggest renaming item1 to items. For this you need to use CodingKeys (it is as a custom mapping between a model and a JSON):
struct Temp: Hashable, Codable, Identifiable {
enum CodingKeys: String, CodingKey {
case id, name, items = "item1"
}
var id: Int
var name: String
var items: [ExampleItem]
}

Issue parsing the json response in swift

I have a json response as follows:
[
{
"item_id": 3310,
"sku": "BWBCL14KWGF003-BWBCL14KWGF003",
"qty": 1,
"name": "BWBCL14KWGF003",
"price": 471,
"product_type": "simple",
"quote_id": "4246",
"product_option": {
"extension_attributes": {
"custom_options": [
{
"option_id": "23243",
"option_value": "625080"
},
{
"option_id": "23242",
"option_value": "625032"
}
]
}
}
}
]
I have the alamofire code to get this response.
AF.request("https://adamas-intl.com/rest/V1/carts/mine/items", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
switch response.result {
case .success(let json):
if let res = json as? [[String: Any]]{
print("res is",res)
}
case let .failure(error):
print(error)
}
I need to fetch the item_id and other values from the response.This way of fetching,iam not able to reach inside the values.
How could i parse this json response?
I think that the best way here is to use a Decodable protocol.
struct Item: Decodable {
var itemId: Int
var sku: String
// ...
}
Then use responseDecodable(_:) method
// create a decoder to handle the `snakeCase` to `camelCase` attributes
// thanks to this `Decoder`, you are able to add a property `var itemId: Int` instead of `var item_id: Int`
let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
AF.request("https://adamas-intl.com/rest/V1/carts/mine/items")
.validate()
.responseDecodable(of: [Item].self, decoder: decoder) { (response) in
guard let items = response.value else { return }
// do what you want
}

Swift: The given data was not valid JSON

Since yesterday when I updated xCode I'm receiving error in the console and I'm not getting my data from the API. I'm getting this error:
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
What I don't understand is that I never had any issues before and now I get this error out of nowhere, and I also don't know if the problem is server sided or in my swift code...
Here if how I make the request:
// -- 1 -- Create an URL
if let url = URL(string: urlString){
// -- 2 -- Create a URLSession
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil{
print(error!)
return
}
if let safeData = data {
self.parseJSON(EventDatas: safeData)
}
}
task.resume()
}
}
func parseJSON(EventDatas: Data){
let decoder = JSONDecoder()
do{
let decodedData = try decoder.decode(LuxCategoryData.self, from: EventDatas)
var test: [Int] = []
for object in decodedData.category {
let category: CategoryData = CategoryData()
category.idCategory = object.idCategory
category.dtTitle = object.dtTitle
dropDown.optionArray.append(category.dtTitle)
test.append(Int(category.idCategory)!)
self.categoryData.append(category)
}
dropDown.optionIds = test
}catch{
print(error)
}
}
Here is the decodable struct I use to parse the JSON:
struct LuxCategoryData : Decodable {
let category: [Category]
}
struct Category: Decodable {
let idCategory: String;
let dtTitle: String;
}
This is how my JSON look like when I make a request in the browser:
{
category: [
{
idCategory: "1",
dtTitle: "Cinema"
},
{
idCategory: "2",
dtTitle: "Bar"
},
{
idCategory: "5",
dtTitle: "Danse"
},
{
idCategory: "6",
dtTitle: "Nightlife"
},
{
idCategory: "10",
dtTitle: "Music"
}
]
}
The JSON you provided doesn't contain " " around the keys. That's why it is giving invalid JSON error.
Try with the below JSON format,
{"category":[{"idCategory":"1","dtTitle":"Cinema"},{"idCategory":"2","dtTitle":"Bar"},{"idCategory":"5","dtTitle":"Danse"},{"idCategory":"6","dtTitle":"Nightlife"},{"idCategory":"10","dtTitle":"Music"}]}
Error: Parse error on line 1:
{ category: [{ idCa
--^
Expecting 'STRING', '}', got 'undefined'
https://jsonlint.com/