ManjunathK
ManjunathK

Reputation: 387

Parsing json response with nested " in swift

I wanted to know the best way to parse json response of below type in Swift 4. Response is double encoded -

\"[{\\"value\\":\\"International University \\\\"MITSO\\\\"\\",\\"id\\":\\"a1v24000000uOrPAAU\\",\\"addlFields\\":[\\"Mi?narodny Universitet \\\\"MITSO\\\\"\\"]}]\"

Here is the data in NSData format -

(String) $R0 = "data: Optional(146 bytes) as NSData: <225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>"

As you see value of the key "value" has a inner double quotes("). JSONSerialization consider this as invalid Json. Any help will be greatly appreciated.

Upvotes: 0

Views: 127

Answers (2)

ManjunathK
ManjunathK

Reputation: 387

@OOPer thank you for the solution. Appreciate you giving your time. Solution worked as expected. Pasting code here which may help others.

Here is how I am doing -

func getData(text:String, callback:@escaping (_ result: Array<somedata>?,_ error:Error?) -> Void) {

    let params = ["search":text]

    getDataSomeAPI(url: "http:\\xyz.com\fdf", params: params) { (result, error) in
        if error == nil {
            do {
                //Response is double encoded
                if let firstDecoded = try JSONSerialization.jsonObject(with: result as! Data, options: .allowFragments) as? String
                {
                    let firstDecodedData = firstDecoded.data(using: .utf8)!
                    if let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData) as? NSArray {

                        var array =  [somedata]()
                        for obj in secondDecoded {
                            Mapper<somedata>().map(JSONObject: obj).then { mappedObj in
                                array.append(mappedObj)
                            }
                        }
                        callback(array,nil)
                    }
                }
            }
            catch  {
                //Handle unexpected data format
                let error = NSError(domain: "",
                                    code: 0,
                                    userInfo: nil)
                let sErr = Error(err: error)
                callback(nil, sErr)
            }
        } else {
            callback(nil, error)
        }
    }
}

Upvotes: 0

OOPer
OOPer

Reputation: 47876

The content of your data as String is as follows:

"[{\"value\":\"International University \\\"MITSO\\\"\",\"id\":\"a1v24000000uOrPAAU\",\"addlFields\":[\"Mi?narodny Universitet \\\"MITSO\\\"\"]}]"

Seeing the actual content without extra double-quotes and backslashes needed to show String as String-literal, it looks like some valid JSON is embedded in a String.

This may happen when the server side code double-encodes the data. You should better tell your server side engineer to fix the issue, but if it is difficult or would take long time, you can double-decode it.

Testing code:

import Foundation

let dataStr = "<225b7b5c 2276616c 75655c22 3a5c2249 6e746572 6e617469 6f6e616c 20556e69 76657273 69747920 5c5c5c22 4d495453 4f5c5c5c 225c222c 5c226964 5c223a5c 22613176 32343030 30303030 754f7250 4141555c 222c5c22 6164646c 4669656c 64735c22 3a5b5c22 4d693f6e 61726f64 6e792055 6e697665 72736974 6574205c 5c5c224d 4954534f 5c5c5c22 5c225d7d 5d22>".dropFirst().dropLast().replacingOccurrences(of: " ", with: "")
let byteArr = stride(from: 0, to: dataStr.count, by: 2).map{(index: Int)->UInt8 in
    let start = dataStr.index(dataStr.startIndex, offsetBy: index)
    let end = dataStr.index(start, offsetBy: 2)
    return UInt8(dataStr[start..<end], radix: 16)!
}
let responseData = Data(bytes: byteArr)
print(responseData as NSData)

Check here, whether the print statement output is exactly the same as your sample response. (If you want to test the following code with your actual data than sample response, use just let responseData = result as! Data instead of above lines.)

So, you just need to use JSONSerialization twice:

block: do {
    let firstDecoded = try JSONSerialization.jsonObject(with: responseData, options: .allowFragments) as! String
    let firstDecodedData = firstDecoded.data(using: .utf8)!
    let secondDecoded = try JSONSerialization.jsonObject(with: firstDecodedData)
    //Code below is an example of using decoded result.
    guard let resultArray = secondDecoded as? [[String: Any]] else {
        print("result is not an Array of Dictionary")
        break block
    }
    print(resultArray)
    if
        let addlFields = resultArray[0]["addlFields"] as? [String],
        let firstAddl = addlFields.first
    {
        print(firstAddl)
    }
} catch {
    print(error)
}

Outputs: (Omitting some output for print(responseData as NSData).)

[["id": a1v24000000uOrPAAU, "value": International University "MITSO", "addlFields": <__NSSingleObjectArrayI 0x100e40c80>(
Mi?narodny Universitet "MITSO"
)
]]
Mi?narodny Universitet "MITSO"

(You may find some parts like <__NSSingleObjectArrayI 0x100e40c80> are strange, but it's just a problem of generating default description and you can access the elements as an Array.)


Anyway, please try and see what you can get with my code above.

Upvotes: 2

Related Questions