4
\$\begingroup\$

I build phone apps and I use JSON a lot in my APIs. The problem is sometimes I don't know how to format the JSON data.

For example, an iOS UITableView takes NSArray as datasource.

{
    "contact_id": [
        "455",
        "464"
    ],
    "contact_name": [
        "Administrator",
        "Main contact"
    ]
}

For me it makes sense since I don't have to loop through anything, but I was told it is not safe to rely on the indices matching the position.

I remember, while asking questions, people bringing to my attention to be careful not to separate records into different arrays, and instead I should return array of objects:

[
    {
        "contact_id": "455",
        "contact_name": "Administrator"
    },
    {
        "contact_id": "464",
        "contact_name": "Main contact"
    }
]

I would like to know the best practice to formatting JSON.

\$\endgroup\$
6
  • \$\begingroup\$ While I prefer the second approach, I don't know what is "unsafe" about the first. One reason to advise against the first is that you have to remember if you ever modify one you have to modify the other as well. \$\endgroup\$ Feb 28 '14 at 3:12
  • 1
    \$\begingroup\$ do you manipulate data on your server or the client application \$\endgroup\$ Feb 28 '14 at 3:14
  • 1
    \$\begingroup\$ Well the first one seems to rely on some particular parsing. The second one is a common structure among what I can see most of the time. I have a question is conact_id and contact_name suppose to be an object or not ? \$\endgroup\$
    – Marc-Andre
    Feb 28 '14 at 3:23
  • 1
    \$\begingroup\$ This question appears to be off-topic because it is about data formatting, not coding. \$\endgroup\$ Jul 2 '14 at 13:28
  • \$\begingroup\$ This question appears to be off-topic because it is asking what the best practices are regarding X. Please see our help center for more information. \$\endgroup\$ Jul 2 '14 at 13:55
3
\$\begingroup\$

Yes, it is not safe to rely on indices.

In your first example you completely rely on that the indices of contact_id and contact_name matches.

So say you would extract the contact_id and contact_name for a single index to get the user like this:

var User = { "Id": contact_id[0], "Name": contact_name[0] }; 
Log.print(User);
>> 455 Administrator

This may be fine and dandy for now, but imagine what will happen when an element is introduced in the wrong index? Say a new developer comes along and decides to insert a new user but s/he is not too careful about where he's introducing or he misinterprets how the JSON should be formatted it and the result is a JSON looking like

{
    "contact_id": [
        "455",
        "456",
        "464"
    ],
    "contact_name": [
        "Administrator",
        "Main contact",
        "Sub Contact"
    ]
}

Here the new developer thought that the contact_id might just be ordered descendingly , and that contact_name should also be ordered descendingly but by its roles (because, once again, s/he misinterprets contact_name to be a list of roles even though the array is named contact_name).

As such he has introduced the new user's id at index 1 and the new user's name at index 2 and suddenly you are left debugging why all of your user's name, id, etc is wrong.

So why is it wrong?

Because you should never trust that either yourself in the future, or someone else will adhere to your arbitrary rules that any given index over all of your arrays are associated.

This is also the reason for why you should not match e.g a role permission on indices solely. Say you got an ordered list of roles

var roles = [
      "Admin",
      "LocalAdmin",
      "ContentManager",
      "User"
];

And in your permission script you check for user.role == roles[0], and sometimes in the future - who is unaware that the permission check that you wrote works like that - decides to add a new user "SuperAdmin", creating the following JSON

var roles = [
      "SuperAdmin",
      "Admin",
      "LocalAdmin",
      "ContentManager",
      "User"
];

By the logic of matches by indices user.role == roles[0], every admin has now become an SuperAdmin even though they shouldn't.

\$\endgroup\$
1
  • \$\begingroup\$ sorry for the delay, this is the answer that I was looking for. \$\endgroup\$ Apr 12 '14 at 21:48
4
\$\begingroup\$

Keep related data together.

  • Does your application need a list of contacts? Keep each contact intact with the second approach. Merging data to create and pass a single contact takes work and is error-prone.
  • Does it need a list of contact IDs? Do the same or return just that list. With the latter approach you can easily produce a limited array. Going the other way is much harder (more code).
  • Does it need a list of contact names in a drop-down that loads the full contact for one ID? Use the latter since you get everything up-front.

The short answer is Keep related data together because it's generally easier to pull it apart than merge it back together.

\$\endgroup\$
2
\$\begingroup\$

The second approach is the best way to go here.

That's because if you think about it, your app needs data that is "A list of contacts". If my OOP concepts aren't rusty, contacts would equate to an object. That would be like new Contact(). A list would equate to an array. Therefore, you need an array of these contact objects. In C#, it would equate to new List<Contact>() - a list of contacts.

Also, since you already know that the data is a list of contacts, the contact_ is unnecessary.

[
    {
        "id": "455",
        "name": "Administrator"
    },
    {
        "id": "464",
        "name": "Main contact"
    }
]

Another issue with the first approach you have is when you loop through the data, whose length are you going to use as basis? For instance you chose contact_id as basis:

var data = {...}
for(var i = 0; i < data.contact_id.length;i++){...}

Now what if you decided to change contact_id to just id. You'd have to scavenge your code and replace data.contact_id.length to data.id.length. Or what if contact_id was taken out entirely? You'd break the loop. Whereas you had an array in the first place:

var data = [];
for(var i = 0; i < data.length;i++){...}

Even if you took out or renamed object properties, that would not break the loop you will be using. Your loop is only concerned with the array. The objects will be handled by the code in the loop and not the loop itself. Saves you 5 mins debugging.

\$\endgroup\$
2
\$\begingroup\$

Remember what JSON stands for, specifically the ON component. Object Notation. It's supposed to describe an object. The object being a contact, the ID and name of the Contact are elements of the same object.

As an iOS developer, I would build a data model to represent the data returned from a JSON API, so I would seed it with the dictionary that is returned from each entry in the array of JSON objects.

[ { 
    "id": 1,
    "name": "Administrator"
  },
  {
    "id": 2,
    "name": "User"
  }
]

So if that is the content of the NSArray, you seed that like:

NSMutableArray* contacts = [NSMutableArray new];

for (NSDictionary* element in dataArray)
{
  Contact* contact = [Contact contactWithDictionary:element];
  [contacts addObject:contact];
}

Consumers of APIs should really always be building data models on the data that is returned from the API. That's my opinion.

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.