Working with JSON – Part 3

Last post was devoted to custom keys and custom objects. In this one let’s talk about top level entities and arrays, very common structures used in JSON files and responses.

Arrays

Working with arrays inside JSON is easy peasy. Let’s take this example:

{
   "productId":1423,
   "name":"Hockey Mask",
   "availableColors":[
      "white",
      "brown",
      "black"
   ],
   "price":39.90
}

The Product object will look like this:

struct Product: Codable {
    let productId: Int
    let name: String
    let availableColors: [String]
    let price: Float
}

As you can see in line 4, we set the property availableColors to an array of String objects.


Let’s take a more complex example:

{
   "productId":1423,
   "name":"Hockey Mask",
   "availableColors":[
      "white",
      "brown",
      "black"
   ],
   "price":39.90,
   "stores":[
      {
         "name":"Gerry Cosby & Co., Inc.",
         "address":"11 Pennsylvania Plaza, New York, NY 10001, United States",
         "phone":"+1 877-563-6464"
      },
      {
         "name":"National Hockey League",
         "address":"1185 6th Ave, New York, NY 10036, United States",
         "phone":"+1 212-789-2000"
      },
      {
         "name":"Modell's Sporting Goods",
         "address":"234 W 42nd St, New York, NY 10036, United States",
         "phone":"+1 212-764-7030"
      }
   ]
}

There’s a new field: “stores”. First, we create a struct for that:

struct Store: Codable {
    let name: String
    let address: String
    let phone: String?
}

Note that we set phone as an optional because we’re assuming the API documentation says that this field may not be in the response for all the entries.

Now let’s use it:

struct Product: Codable {
    let productId: Int
    let name: String
    let availableColors: [String]
    let price: Float
    let stores: [Store]
}

As simple as that. Look at line number 6: we declare the property stores as an Array of Store.

Top level entities

APIs tend to use a wrapper key so that the top level entity of the JSON is an object. Let’s suppose we’re requesting the available stores and we get this response:

{
   "stores":[
      {
         "name":"Gerry Cosby & Co., Inc.",
         "address":"11 Pennsylvania Plaza, New York, NY 10001, United States",
         "phone":"+1 877-563-6464"
      },
      {
         "name":"National Hockey League",
         "address":"1185 6th Ave, New York, NY 10036, United States",
         "phone":"+1 212-789-2000"
      },
      {
         "name":"Modell's Sporting Goods",
         "address":"234 W 42nd St, New York, NY 10036, United States",
         "phone":"+1 212-764-7030"
      }
   ]
}

As you can see, the top level entity is the field stores which is an Array of Store. There’s nothing new here: we’ve just seen that in the section above. Here’s how we may deal with it:

struct StoresList: Codable {
    let stores: [Store]
}

That’s it.


But what if there’s no wrapper key? The JSON may look like this:

[
   {
      "name":"Gerry Cosby & Co., Inc.",
      "address":"11 Pennsylvania Plaza, New York, NY 10001, United States",
      "phone":"+1 877-563-6464"
   },
   {
      "name":"National Hockey League",
      "address":"1185 6th Ave, New York, NY 10036, United States",
      "phone":"+1 212-789-2000"
   },
   {
      "name":"Modell's Sporting Goods",
      "address":"234 W 42nd St, New York, NY 10036, United States",
      "phone":"+1 212-764-7030"
   }
]

Note that line 1 is not a curly brace: it’s a bracket, thus it’s an array. How we decode this? Very simple:

let decoder = JSONDecoder()
if let stores = try? decoder.decode([Store].self, from: jsonData) {
    print(stores)
}

Look at line 2: [Store].self is telling the decoder that the top level object is an Array of Store. Pretty neat, huh?


There’s another possible scenario that might give you the creeps. Take a look at this:

[
   {
      "Gerry Cosby & Co., Inc.":{
         "address":"11 Pennsylvania Plaza, New York, NY 10001, United States",
         "phone":"+1 877-563-6464"
      },
      "National Hockey League":{
         "address":"1185 6th Ave, New York, NY 10036, United States",
         "phone":"+1 212-789-2000"
      },
      "Modell's Sporting Goods":{
         "address":"234 W 42nd St, New York, NY 10036, United States",
         "phone":"+1 212-764-7030"
      }
   }
]

Don’t panic! Let’s analyze it a little bit. Start reading from above: the JSON is an array with contains entities wrapped with different keys. So “Gerry Cosby…” wraps an address and a phone and so it does “National Hockey…” and “Model’s Sporting…” as well. Array of entities wrapped with a key.

Let’s create that entity first:

struct Store: Codable {
    let address: String
    let phone: String?
}

Each Store entity is wrapped with a key so that will look like String: Store, won’t it?

Now, we have all the stores listed in an array (look at lines 2 and 15). We express that by writing [String: Store].

But look at lines 1 and 16: the top level entity is an array as well (in this case with only one entry –[String: Store]– but an array it is), which gives us this structure: [[String: Store]].

Here’s how we decode it:

let decoder = JSONDecoder()
if let stores = try? decoder.decode([[String:Store]].self, from: jsonData) {
    print(stores)
}

We tell the decoder that our top level entity is of type [[String: Store]].

Is that it?

No. There’s much more. Learn how to create your own implementations of the Decodable and Encodable protocols.