Questions about JSON conversion to model objects

After finishing this chapter I’m either missing something or confused. On page 323 (at the top of the page) of this chapter in our do/catch loop we convert the JSON data object into a model object. Then we break it down to its simplistic parts to get individual model objects.

   static func photosFromJSONData(data: NSData) -> PhotoResult {
do {
let jsonObject: AnyObject = 
try NSJSONSerialization.JSONObjectWithData(data, options: [])

guard let 
jsonDictionary = jsonObject as? [NSObject: AnyObject],
photos = jsonDictionary["photos"] as? [String: Anyobject],
photosArray = photos["photo"] as? [[String: AnyObject]] else {

etc. etc...

  }
}

My question is, when i print the jsonObject variable (in other words the original JSON model object with all its nested data) in its full format, I don’t find that the top-level dictionary has any key. It only has values (They refer to other key-value objects which are nested). So when we do our first call:

jsonDictionary = jsonObject as? [NSObject: AnyObject]

What does the NSObject key refer to?

Second Question:

In Xcode when printing out the full JSON model object, the array objects are nested inside parentheses ( ). The book however, on page 318 shows that arrays have brackets. Which one is the case? Or is it Xcode’s debugger just showing it with parentheses?

Heres a cropped version of JSON data printed to Xcode’s debugger:

I would appreciate if anyone could provide an answer.

In our do/catch loop we convert the JSON data object into a model object. Then we break it down to its simplistic parts to get individual model objects.

  1. Ehh. do/catch isn’t a loop. do/catch block would be a more appropriate description.

  2. Your use of the terms model object and individual model objects is confusing to me. Our model is Photo, but JSONObjectWithData(_:options:) returns an AnyObject type–not a Photo. The AnyObject could be an Array or it could be a Dictionary. Then we try to convert the AnyObject to a Dictionary. If that succeeds, i.e. when it’s not an Array, then we lookup info in the Dictionary.

A few days ago, I printed out some raw JSON, and I was confused by the syntax as well, so I searched around for awhile, and I managed to piece together the syntax rules for the json output:

(  )      NSArray
{  }      NSDictionary
 =        Key/Value separator in a dictionary (like `:` in Swift)
" "       If a string dictionary key consists entirely of letters and numbers, 
          then it's not quoted, otherwise it's quoted.

So, if you look at the output you posted again, you should realize that the whole jsonObject is a dictionary, and it has the key photos = at the top level. In other words, you have a structure like the following in Swift syntax:

[
     "photos": [
         "key": val
     ]
]

In old time Objective-C, which is what iOS programs were originally written in, I don’t believe there were any array/dictionary literals. To create an array/dictionary you called some method:

   dictionaryWithObjectsAndKeys: ....

As a result, there was no syntax in the code like ( ), { }, or [ ] associated with a collection or even a key/value separator. But for output, something was needed to represent each collection type.

when we do our first call:

jsonDictionary = jsonObject as? [NSObject: AnyObject]

What does the NSObject key refer to?

The keys in the top level dictionary. However, the JSON spec requires that dictionary keys be strings, so I’m not sure why the book casts to the more general NSObject when we know that the JSON data we are getting should have strings for the keys in dictionaries. So why not cast to [NSString: AnyObject] or even [String: AnyObject] (both work by the way)? If the JSON data we receive doesn’t have strings for keys, then we don’t know how to parse it(because it’s not JSON), so we might as well fail right there.

Then there’s the niggling detail of why you wouldn’t cast to [AnyObject: AnyObject] when you want to cast to the most general dictionary type. If you try it, you’ll get an error. That’s because the keys in a dictionary have to be hashable, which is a computer programming term for successfully being able to run a key through a function and get a unique number back. It turns out that the type AnyObject isn’t hashable(for some reason) while NSObject is, so NSObject is the most general type you can use for a key in a dictionary.

Thank you for your answer. I now understand all except one last thing.

What about the first braces that delimits a JSON dictionary in my Xcode debugger. It has no key to it? Only the key nested inside the outer most dictionary has the key “photos.” So when we call:

jsonDictionary = jsonObject as? [NSObject: AnyObject] ,

there is no key for NSObject of this dictionary to refer to?

So when we call:

jsonDictionary = jsonObject as? [NSObject: AnyObject] ,

there is no key for NSObject of this dictionary to refer to?

It appears that you think the cast works like this:

          jsonObject: AnyObject
               |
               V
[NSObject: AnyObject]

and then Swift looks for a match for NSObject outside of jsonObject (as well as a surrounding dictionary represented by [ ]). But what really happens is that Swift introspects jsonObject itself and looks for NSObject: AnyObject pairs inside of jsonObject (which itself is to be considered a dictionary [ ]). It’s just coincidence that jsonObject’s type is mentioned in the cast type [NSObject: AnyObject]. For example:

let x: AnyObject = "hello"
//print(x.characters.count)    //error=> AnyObject has no member 'characters'

let myString = x as? String   //The cast type String does not match x's type
                              //AnyObject, yet the cast succeeds.
if let myString = myString {
    //You casted to String type, so that you can call String methods/properties:
    print(myString.characters.count)  //=>5
}

Another example:

func doStuff() -> AnyObject {
    return ["photos": ["a":1] ]
}

let d = doStuff() as? [String: [String: Int]]   //AnyObject doesn't match anything in the cast type
                                                //yet the cast succeeds...

if let d = d,
       photos = d["photos"],
       a = photos["a"]
{
    print(a)

}

--output:--
1

The reason we are casting jsonObject to [NSObject: AnyObject] is that jsonObject was previously labeled as some unknown type AnyObject (because json’s outer container can be an array or a dictionary), and we want to be able to treat that unkown object as a dictionary. And the reason AnyObject is used for the values in the cast type is because in general the photos key in the json could be followed by another key whose value is an Array, followed by another key whose value is an Int, followed by another key whose value is a String, etc. For instance:

{
    photos = {a = 1},
    xif_data = (1, 2, 3),
    count = 50
    "request_date" = "2016-12-25 12:30:00"
}

So in order for the cast to a Dictionary to succeed, we need to specify AnyObject for the values of the top level keys.

Thank you. That sums it all up.