Search code examples
swift4jsondecoder

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?


Solution

  • 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)