0

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";
        }
ccurelea
  • 7
  • 3

2 Answers2

0

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)
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47
  • The print statement returns nothing. The outline of the JSON data can be viewed here: https://firebasestorage.googleapis.com/v0/b/faceanalysis-3f016.appspot.com/o/39B76326-6636-4CD4-98F0-4EE3707062FF?alt=media&token=bff3a836-7198-4203-aeeb-f2203c17ea7a I think the path I am using is incorrect. I need to get only points – ccurelea May 29 '20 at 16:11
  • Seems like face is an array, so you need to handle them as `Array` and not `Dictionary`. I'll update my answer checkout. – Frankenstein May 29 '20 at 16:14
  • Thank you should I delete one of my posts to avoid getting flagged? I am almost at 15 points then I will up post! Thank you so much for your help! – ccurelea May 29 '20 at 16:22
  • Since there is enough contribution it would be better if you kept it. And don't worry about points, just keep coding and ask questions (after doing research if the question was already asked by someone else) that's how you learn. – Frankenstein May 29 '20 at 16:26
  • Better to use the newer `JSONDecoder` instead of `JSONSerializer`. – koen May 29 '20 at 16:45
  • @koen Yes, you are right, I know that's the way moving forward. But he's a beginner and there's enough information here for him to grasp. So, one step at a time, right? – Frankenstein May 29 '20 at 16:49
0

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.