Ihor Makhnyk
Ihor Makhnyk

Reputation: 3

How to decode NaN value from JSON using Swift?

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?

Upvotes: 0

Views: 825

Answers (2)

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 []
    }
}

Upvotes: 2

nrurnru
nrurnru

Reputation: 51

You can set the option of JSONDecoder to try decode not a number value. This option contains case of infinity too.


let decoder = JSONDecoder()
decoder.nonConformingFloatDecodingStrategy = .convertFromString(
    positiveInfinity: "Infinity",
    negativeInfinity: "-Infinity",
    nan: "NaN"
)

Upvotes: 3

Related Questions