Ramis
Ramis

Reputation: 16549

How to decode custom JSON values using JSONDecoder

Backend returns custom JSON value for location. As shown in example:

{
    "location": (54.000000, 21.000000)
}

For parsing JSON I am using this code:

let json = """
{
    "location": (54.000000, 21.000000)
}
"""    
struct Location: Codable {
    var latitude: Double
    var longitude: Double
}
let dataJson = json.data(using: .utf8)!
let location = try? JSONDecoder().decode(Location.self, from: dataJson)

When I am trying to create Location object using JSONDecoder it gives me an error: The given data was not valid JSON.

dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 18." UserInfo={NSDebugDescription=Invalid value around character 18.})))

I know that it is not valid JSON. Which methods to override that I could parse invalid JSON values?

Upvotes: 3

Views: 9089

Answers (1)

Mike Henderson
Mike Henderson

Reputation: 2132

If the third party generates the invalid JSON in a consistent manner, you can use regex to to make fix it back to valid JSON. This is not fool-proof. It can fail if the JSON is simply formatted differently. The best course of action is to ask the third party to correct their back-end.

You can use regex to replace the round brackets with square brackets:

var json = """
{
"location": (54.000000, 21.000000)
}
"""

let regex = try! NSRegularExpression(pattern: "\\\"location\\\":\\s*\\((.+?)\\)", options: [])
let fullRange = NSRange(..<json.endIndex, in: json)

json = regex.stringByReplacingMatches(in: json, options: [], range: fullRange, withTemplate: "\"location\": [$1]")

You also need to add a custom decoder to your Location struct since it is encoded as an array now:

struct Location: Decodable {
    var latitude: Double
    var longitude: Double

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        latitude = try container.decode(Double.self)
        longitude = try container.decode(Double.self)
    }
}

Example of decoding:

struct Response: Decodable {
    var location: Location
}
let dataJson = json.data(using: .utf8)!
let location = try JSONDecoder().decode(Response.self, from: dataJson)

Upvotes: 5

Related Questions