Regular problem with XML to JSON Converters

Profile
Written by dermdaly on February 22nd, 2011

Here at tapadoo, we’ve done a number of apps which communicate with back end data servers. Typically this means a RESTful API with JSON or XML payloads.
There’s one issue we’ve come across on a number of projects, where we’re dealing with an existing Web API which is presenting the payloads in JSON.
The problem is where arrays are incorrectly serialized in certain cases. Specifically we’ve seen:

Firstly, lets explain why this has occurred
If you consider the following XML:

<team>
    <employee>
      <name>Joe</name>
      <surname>Bloggs</surname>
  </employee>
  <employee>
    <name>Jane</name>
    <surname>Doe</surname>
  </employee>
</team>

A typical XML to JSON converter will convert this as follows:

{
  team:{
    employee:[
      {
        name:'Joe',
        surname:'Bloggs'
      },
      {
        name:'Jane',
        surname:'Doe'
      }
    ]
  }
}

I.e It is converted to a Dictionary, with a key called ‘team’ whose value is a Dictionary, with a key called ’employee’ which is an array of Dictionaries (with keys of ‘name’ and ‘surname’).

Great.
However if the XML was a team with a single entry:

<team>
    <employee>
      <name>Joe</name>
      <surname>Bloggs</surname>
  </employee>
</team>

The XML to JSON converter has no context on Employee. It has now way of knowing that this is potentially a repeating group (especially if it has no XSD to consult), so it converts the XML to the following JSON:

{
  team:{
    employee:{
      name:'Joe',
      surname:'Bloggs'
    }
  }
}

So, this is now a Dictionary, with a key called ‘team’ whose value is a dictionary with a key called ’employee’ whose value is a Dictionary.

The problem here is when we go to parse, we expect the value of ’employee’ to be an array (which it isn’t), and this can lead to problems in the code.

And..due to Objective-C loose approach to type, the following code will run:

NSArray *array = [teamDict objectForKey:@"employee"];

In those cases where there team has a number of employees, the above code works fine, and the pointer, array stores an array. In those cases where a team as a single employee, the above code runs, however the pointer, array will actually point to a NSDictionary. So the following code (for example)

int numEntries = [array count];

Will sometimes work, and sometimes lead to a runtime error, which on iPhone will manifest itself as a crash.

So how do we fix this?
Well, if you have control over the server side, the simple question is to fix it in the first place. Note: This is a common problem with JAXB, a popular java XML binding mechanism often used in REST services written in Java. We’ve come across some examples on how to work around it here and here.

If you have no control over this, you may be able to handle this in your Objective-C code. Here’s some sample code we’ve started to use; It checks the data type at runtime, and if necessary, converts it to a single element array

+(NSArray *) getArrayFromJSONDictionary:(NSDictionary *)parent 
               forKey:(NSString *)key {
  id obj = [parent objectForKey:key];
  if([obj isKindOfClass:[NSArray class]]) {
    return obj;
  }
  if([obj isEqual:[NSNull null]]) {
    NSLog(@"Warning: object for key %@ is null", key);
    // Return an empty array
    return [[[NSArray alloc] init] autorelease];
  }
  NSLog(@"Warning object for key %@ is of type %@",
        key, [[obj class] description]);
  NSArray *ret = [NSArray arrayWithObject:obj];
  return ret;
}

Comments (6)

Martin says:

While developing apps, just like these, I have also met this issue. Although, I’ve always seen it the other way around. JSON in this case has an avantage when used with objective-c, since the principals of storing is so similar (JSON-array->NSArray and JSON-object->NSDictionary). I’m having problems parsing XML code in objective-c for this very reason… When is the object an array and when is it a dictionary…
Anyhow, I’m kinda explaining the confer of your post back at you here. The only thing I’m really saying is that you name this a problem with JSON, I’d say it’s a problem with XML. Then again, I’m not really a XML fan 😉

Dave Winer had much the same problem; http://scripting.com/stories/2010/12/18/questionForJsonGurus.html

Thanks for the iOS solution.

mm dermdaly says:

Interestingly, more than one person on twitter has suggested that its a bug in the API, and therefore we should make the API owner fix their API.
This is a blinkered answer. If you’re in the business of writing software for clients, you’ll know that you often have to work with whatever is presented to you. Of course you can highlight the API issue, and suggest it may be fixed, but remember you may be dealing with a legacy system, or something that has had a great deal of bedding in…and here you are writing new client side software where you’re in the position to work around API shortcomings.
In short: If the client is willing to fix the API, great; If not we use techniques like the one above.

nomel says:

The json converter does have context to the xml, because the xml should be following a schema! In the xml schema, if ‘maxOccur=”unbounded”‘, then it should return an array, no matter how few entries. Not having or following the schema is why these converters are breaking! As you mentioned, it’s an impossible task because the json doesn’t have any context…but only because it’s ignoring the context (the xml schema).

mm dermdaly says:

Good point; thanks for commenting. What if the entry said “maxOccurs=1”? Could we end p with the same problem?

Ranga Reddy Tirupati says:

Though we are using maxOccurs=”unbounded” we are not able to get single object as an Array. Is that ok if we use json:Array=’true’ at element declaration?

Comment on this post

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close