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.
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.
#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)
}
}
}
Related
I am trying to parse some JSON data returned from an API call. The path I want to navigate is media > faces > points. I can navigate to faces but getting points is the issue.
here is code
let dictionary = try JSONSerialization.jsonObject(with: data!, options:.mutableContainers) as? [String:Any]
let media = dictionary!["media"] as? [String: Any]
print(media!["faces"]) // Works see the returned data below
let faces = media!["faces"] as? [String: Any]
print(faces!["points"]) // Thread 4: Fatal error: Unexpectedly found nil while unwrapping an Optional value
API returned data for print(media!["faces"])
{
angle = "1.2222";
"appearance_id" = 0;
"detection_score" = 1;
duration = "00:00:00";
"face_uuid" = "78338d20-9ced-11ea-b153-0cc47a6c4dbd";
height = "74.31999999999999";
"media_uuid" = "fec83ac3-de00-44f0-ad5b-e1e990a29a8c";
"person_id" = "";
points = (
{
name = "basic eye left";
type = 512;
x = "85.16";
y = "86.62";
},
{
name = "basic eye right";
type = 768;
x = "110.92";
y = "86.66";
}
The main problem here is that you are force unwrapping optionals that you can't guarantee have a value - and when they don't have one that's causing a fatal error.
A better approach is to unwrap the variables safely and throw an error if you need to handle it. I've tried to show a few different ways to do that below.
guard let data = data else { return }
do {
let dictionary = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any]
guard let media = dictionary?["media"] as? [String: Any] else { throw MyError.mediaNotFoundError }
if let faces = media["faces"] as? [String: Any] {
print(faces)
guard let points = faces["points"] else { throw MyError.pointsNotFound}
print(points)
} else {
throw MyError.facesNotFound
}
} catch {
// Handle error here
}
Note, I have used a custom error enum. I would also advise adding a localised description to it in order to add more details for debugging.
enum MyError: Error {
case mediaNotFoundError
case facesNotFound
case pointsNotFound
}
I would also recommend reading this answer here for a more in-depth explanation on optionals.
Since you are a beginner I'm helping you out. These questions have been asked many times (There is a high chance you get duplicate flag). Here is how you avoid these kinds of errors, try not to use force-unwraps (this symbol !) anywhere without any exception to avoid these errors. Instead of if let you can use guard let, it's a personal choice.
if let data = data,
let dictionary = try JSONSerialization.jsonObject(with: data, options:.mutableContainers) as? [String: Any],
let media = dictionary["media"] as? [String: Any],
let faces = media["faces"] as? [[String: Any]],
let points = faces[0]["points"] {
print(points)
}
Edit: Since "faces" parameter is an array you need to parse them as an Array and not as Dictionary. The above example shows how to get to the first set of "points".
Update: Better approach would be to use JSONDecoder. Here's how:
Create this struct outside the class:
struct Response: Codable { var media: Media }
struct Media: Codable { var faces: [Face] }
struct Face: Codable { var points: [Point] }
struct Point: Codable { }
And then decode like this:
if let data = data,
let response = try JSONDecoder().decode(Response.self, from: data) {
print(response.media.faces[0].points)
}
I have a JSON array created using this call:
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [Any] else {
print("This is not JSON!!!")
return
}
I am trying to get elements from the JSON objects in the array to display them using the following code:
struct sWidget: Codable{
var createdBy: String
var createdDate: Date
var status: String
var widgetNumber: String
var updatedBy: String
var updatedDate: Date
}
do {
let decoder = JSONDecoder()
for (index, value) in json.enumerated() {
let currentWidget = try decoder.decode(sWidget.self, from: json[index] as! Data)
let currentNum = currentWidget.widgetNumber
//print(currentNum)
widgetNums.append(currentNum)
}
}
catch {
print("decoding error")
}
The code compiles but when I run it I get this error in the output:
Could not cast value of type '__NSDictionaryM' (0x1063c34f8) to
'NSData' (0x1063c1090). 2018-08-09 09:41:02.666713-0500
TruckMeterLogScanner[14259:1223764] Could not cast value of type
'__NSDictionaryM' (0x1063c34f8) to 'NSData' (0x1063c1090).
I am still investigating but any tips would be helpful.
Did you try that fetching objects like above mentioned? Because i see that you are using Codable. Fetching is very simple with that actually.
let yourObjectArray = JSONDecoder().decode([sWidget].self, data: json as! Data)
May be this line can be buggy but you can fetch them with one line.
Extending #Cemal BAYRI's answer:
JSONDecoder() throws, so make sure to either us try? or try (don't forget do-catch with try)
guard let data = content as? Data else {
return [sWidget]()
}
let jsonDecoder = JSONDecoder()
1. try?
let yourObjectArray = try? jsonDecoder.decode([sWidget].self, data: data)
2. try
do {
let yourObjectArray = try jsonDecoder.decode([sWidget].self, data: data)
} catch let error {
}
Note: You would need to take care of Data and Date formatting. Below is an example for Date:
jsonDecoder.dateDecodingStrategy = .iso8601
You can also check it out here
I have the following swift3 code. The JSON can return a NSNull value for the $0[2] value.
struct Player3 {
let name : String
var score : String
let avatar : String
}
class HistoricLeagueVC: UITableViewController {
var players = [Player3]()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://str8red.com/jsonoverallleaderboard/1025/"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("there was an error")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[String]]
self.players = parsedData.map { Player3(name: $0[0], score: $0[1], avatar: $0[2]) }
print(self.players.count)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error as NSError {
print(error)
}
}
}.resume()
}
I have tried to convert it to an empty string without success. I have also tried to set a default string such as "https://str8red.com/static/no_profile_picture.png" which ideally is what I would like to do.
The error in the terminal states Could not cast value of type 'NSNull' (0xed1c78) to 'NSString' (0x57b6b8).
Any help would be appreciated.
Do not cast to [[String]] then:
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [[Any]]
self.players = parsedData.map { Player3(
name: ($0[0] as? String) ?? "",
score: ($0[1] as? String) ?? "",
avatar: $0[2] as? String, // keeping `nil` here
)}
You should probably even as! [[Any]] do in a safer manner (using as?) but the above will work.
Your code will crash brutally if anything goes wrong with your input data. Ask your boss if he or she is Ok with that. I wouldn't. Here's how you will crash: If data == nil (but it shouldn't be if error == nil). You catch the problem that data cannot be parsed without crashing. You crash if data doesn't parse to an array, you crash if the elements of the array are not all arrays, you crash if the elements inside the inner arrays are not all Strings, and you crash if any of the inner arrays contain two or fewer strings. I'd say that is pretty unacceptable.
My strategy would be: If the response cannot be parsed or is not an array of arrays, then ignore it, leaving self.players unchanged. If there is an array of arrays, reset self.players, then add a player for each array that contains at least three items, of which the first two must be strings.
guard data != nil else { return }
guard let parsed = try? NSJSONSerialization(with: data!) else { return }
guard let arrays = parsed as? [[Any]] else { return }
self.players.removeAll()
for array in arrays {
guard array.count >= 3,
let name = array [0] as? String,
let score = array [1] as? String else { continue }
let avatar = array [2] as? String ?? ""
self.players.append(Player3 (name: name, score: score, avatar: avatar))
}
It's a good idea to move something like this into a separate method. Add whatever error handling or logging you feel is appropriate.
I am facing error while trying to parse JSON from a URL.
Following is the code:
override func viewDidLoad() {
super.viewDidLoad()
print("hello")
let url=NSURL(string:"http://jsonReports/sample_testkrtk252.json")
print("hello2")
if let allContactsData=NSData(contentsOfURL:url!)
{
print(allContactsData)
do{
let allContacts: AnyObject! = try NSJSONSerialization.JSONObjectWithData(allContactsData, options: NSJSONReadingOptions.MutableContainers)
print(allContacts)
if let json = allContacts as? Array<AnyObject> {
print(json)
for index in 0...json.count-1 {
let data12 : AnyObject? = json[index]
print(data12)
let collection = data12! as! Dictionary<String, AnyObject>
print(collection)
print(collection["data11"])
let data11 : AnyObject? = collection["data11"]
let cont : AnyObject? = collection["cont"]
data1.append(data11 as! String)
data2.append(cont as! String)
print(data1)
print(data2)
}
}
}
catch
{
print("error")
}
}
And after the successful run it gives the following output:
hello
hello2
<7b0d0a20 20202022 ... >
error
(lldb)
Also, it gives the error:
NSError Domain: "NSCocoaErrorDomain" - code: 3840 0x00007f8b62c9ef50 _userInfo __NSDictionaryI * 1 key/value pair 0x00007f8b62dcb3c0
It will be great if someone can look into this. Also, is there any better way to parse JSON in Swift?
your file .json is not a valid structure. Delete the first line:
"registration_id":APA91bEVsOgzkFFDCuEFn8PAS-FQqeaW6TRuz09CeKSnAJUSJmTvP8ubIsUkUe2zOzz8vk-FNqbNteOcD6m8m5nhrNWA96swZHYyX4nvY-mPCJTeBkEXTLuLwWCglbAUVCqJlwharLLJ,
Now try to run the code:
func run()->(Void){
//insert you file .jso into project. Or change the code
let filePath = NSBundle.mainBundle().pathForResource("sample_testkrtk252", ofType:"json")
let nsMutData:NSData = NSData(contentsOfFile:filePath!)!
do{
let allContacts:[String:AnyObject] = try NSJSONSerialization.JSONObjectWithData(nsMutData, options: NSJSONReadingOptions.MutableContainers) as! [String : AnyObject]
print(allContacts)
for temp in 0...allContacts.count-1 {
print(temp)
}
} catch
{
print("error")
}
}
Why not directly casting the AnyObject returned JSONObjectWithData to an array of NSDictionary? Like this:
let allContacts = try NSJSONSerialization.JSONObjectWithData(allContactsData, options: NSJSONReadingOptions.MutableContainers) as! [NSDictionary]
Are you sure your JSON is valid? Swift is very sensitive about that. Make sure all keys are quoted and you didn't forget any braces (especially the most outer ones).
Also for JSON parsing I would recommend SwiftyJSON which makes things a lot easier.
Thanks for your answers.
just editing the json worked for me.
yes, it is true that one needs to follow the format correctly, otherwise a simple double quote can disrupt the expected output. :)
"registration_id":"APA91bEVsOgzkFFDCuEFn8PAS-FQqeaW6TRuz09CeKSnAJUSJmTvP8ubIsUkUe2zOzz8vk-FNqbNteOcD6m8m5nhrNWA96swZHYyX4nvY-mPCJTeBkEXTLuLwWCglbAUVCqJlwharLLJ",
Thanks,
Poulami
I'm trying to parse Json Data from an API :
{
"title": "Mr. Robot",
"first_aired": "2015-06-24",
"network": "USA Network",
"channels": [
{
"id": 12,
"name": "USA",
"short_name": "usa",
"channel_type": "television"
}
],
The Code I'm use is:
var TVArray : [TVInfo] = []
var task : NSURLSessionTask?
func getJSON (urlString: String) {
let url = NSURL(string: urlString)!
let session = NSURLSession.sharedSession()
task = session.dataTaskWithURL(url) {(data, response, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error == nil) {
self.updateJSON(data)
}
else {
}
}
}
task!.resume()
}
func updateJSON (data: NSData!) {
let JSONData = (try! NSJSONSerialization.JSONObjectWithData(data, options: []))
TVArray.removeAll(keepCapacity: true)
if let jsonArray = JSONData {
for j in jsonArray {
let title = jsonResult["title"] as! String
let firstAired = jsonResult["first_aired"] as! String
let network = jsonResult["network"] as! String
let channelName = JsonResult["channels"][0]["name"] as! String
let TV = TVInfo(title: title, firstAired: firstAired, network: network, channelName: channelName)
TVArray.append(TV)
}
}
collectionview.reloadData()
}
}
When I use the above code I get an error 'Initializer for conditional binding must have Optional type, not 'AnyObject'' in front of the line 'if let jsonArray = JsonData'. I've tried some methods I've seen on StackOverflow like the method in the link :
[Parsing JSON in swift 2.0
but it didn't work for me. I'm still a bit new to Swift, I really don't want to use SwiftyJSON. Is this the best way to parse this JSON data or is there a better way of doing it?
Since you've used NSJSONSerialization with try! (note the !, meaning it was forced) the value of JSONData is not an optional: you don't have to unwrap it with if let jsonArray = JSONData.
If you still want an optional value, use try? instead.
Otherwise you could also use try inside a do catch block to handle possible errors.
The type of JSONData is unknown, it needs to be known to be an Array for the following for loop.
use:
let JSONData = try! NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! NSArray
You do not need:
if let jsonArray = JSONData {
because you have already crashed if JSONData is nil from the preceding try!
You are better with:
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data!, options:[]) as! NSArray
for j in jsonArray {
// ...
}
} catch {
// handle error
}
Because you have no control over the JSON you receive and crashing because of a server change is not a good idea.
Also really put some time into naming variables, JSONData is not data, it is an array obtained by parsing a JSON string.