Wednesday, May 13, 2009

iPhone Web Service Toolkit Upgrade: JSON FTW

I have recently open-sourced the ZergSupport code updates used in StockPlay versions 0.3 and 0.4. The high-visibility high-impact change is support for JSON parsing. This post shows what you can do with JSON parsing.

Compact Collection Initialization
Unfortunately, Objective C does not have literals for collections (arrays, dictionaries, or sets). Setting up complex nested structures normally requires ugly objective C code. Fortunately, the whole JSON specification is a compact literal notation. Compare and contrast the following.



To sweeten the deal even more, ZergSupport's JSON parser was extended so you can conveniently embed literals in Objective C strings. First, strings can be delimited by ' (single quotes) asides from the standard delimiter " (double quotes). Second, the extended parser understands sets, which look like arrays, but are delimited by angled brackets ( < > ) instead of square brackets ( [ ] ). Without further ado, here's how to use JSON literal support.



A small wrinkle to be aware of is that JSON parsing is slower than building the collections directly in Objective C, so JSON literals should be used in features that are not performance-sensistive, like tests and configuration files.

Web Services
The API for working with JSON Web services is very similar to the API for XML services, which is showcased in my first post on ZergSupport. When used from the Web service API, the JSON parser ignores everything up to the first { character, so it is able to parse JSONP output.

The code below is a complete implementation for stock ticker symbol search, using Yahoo Finance. The code uses Object Query (covered below) to indicate which parts of the JSON response should be transformed into ModelSupport models.



Object Query
Object Query implements a domain-specific language (DSL) for retrieving objects from a structure of deeply nested Cocoa collections. Queries are specified as strings, and are performed against the root object in a structure of nested collections. The result of a query is an array of zero or more objects matching the query.

The queries are property names, joined by a separator character (usually /). The first character in the query is the separator character. For example /results/1 matches the value that object['results'][1] would return in JavaScript. The special property names * and ? are inspired from glob expressions: * means the next property may be found in the current object or in some descendant object (some levels deeper in the object graph), whereas ? means the next property may be found in the current object or in the object's direct children (one level deeper in the object graph). For example /?/1 will return the same result as /results/1, if the initial object is {'results':['Jane','John']} (the result will be a NSArray containing the NSString @"John").

ObjectQuery can be used directly, outside of JsonHttpRequest, as shown in the following code sample.



The decision to introduce a DSL is motivated by the need to extract ModelSupport models from JSON Web service responses. XML objects have a tag/content separation, and tags are usually good indicators for model extraction purposes. For JSON objects, the closest equivalent to a tag name would be the property name whose value is the object hash describing the model. This is fragile, and does not work if the response contains an array of models. ObjectQuery is a bit more general than name tags, but does not degenerate into XPath's complexity. The implementation is 182 lines of Objective C, including comments and whitespace.

Conclusion
JSON support does not stop at the parser. The toolkit fully embraces JSON, which is now available for initializing ModelSupport models. The toolkit's new Object Query bridges the gap between XML-based and JSON-based Web Services, and preserves API consistency in WebSupport.

Thank you reading this post! I'm looking forward to receiving your feedback or (even better) pull requests for ZergSupport.

13 comments:

  1. Hi,

    I just started using the toolkit and I am having some success. I keep building on my application since I am new to the iphone sdk. Is it possible to retrieve objects in objects? What I a mean is I have a property that is an object its self. The server side is java so I am quite flexible to what I can send via json.

    Thanks,
    Chris

    ReplyDelete
  2. @Chris: It doesn't handle that yet, but it should.

    I think the most natural way would be to have a property whose type is a model, and pass a dictionary through JSON. Do you think that makes sense?

    Will hack something up.

    ReplyDelete
  3. @Chris: try the "submodels" branch in the repository.

    If it works for you, I'll promote it to master.

    ReplyDelete
  4. I am not sure I grasped your thought.

    Example :
    @interface Event : ZNModel {
    NSString *pkId;
    ApplUser *applUser;
    NSString *project;
    NSString *note;
    ApplUser *consultant;
    NSDate *startDate;
    NSDate *endDate;
    }

    I think what you are suggesting would be to make applUser a ZNModel? Then perhaps the toolkit would get smart and see the property and stuff the values?

    Side question, does the xml library handle that? I thought I saw an example where you map class names to properties.

    ReplyDelete
  5. @Chris: Thank you so much for your feedback!

    You are right, in that I was thinking AppIUser would inherit from ZNModel as well.

    You are also right about this not working in XML. The problem is I don't parse nested trees. For example, in the tree below, I wouldn't get to "name":

    < event >
    < appIUser >
    < name >...
    < /appIUser >
    ...
    < /event >

    I'll look into the XML parsing code tomorrow. Can you work with just having JSON support for now?

    ReplyDelete
  6. Hi Victor,

    Thanks for your input as well. Yes, I haven't even tried the xml stuff yet. I guess the use case is really the same between the two apis. You have various properties you may want to "map" to specific classes. But I think as long as your model objects inherit ZNModel we should be ok. I will have to test your branch...ill have a look tonight.

    ReplyDelete
  7. Hi Victor, I downloaded the branch and was looking at the diff. I was wondering if there was a test to have a look at? I guess I was unsure of how it loads the sub object and how you access its properties.

    @interface Event : ZNModel {
    Project *theProject

    }

    @interface Project : ZNModel {
    NSString *name;
    }


    So I query the list of event objects, the event is in Json looks like this:

    {"events":[{
    "pkId": 1026,
    "applUser": "Chris Palmer",
    "project": {
    "pkId": 7,
    "name": "Conn3cted - Toyota",
    "managerName": "Glenn Murray"
    },
    "startDate": "2009-06-01 09:00 AM",
    "endDate": "2009-06-01 05:00 PM",
    "note": "Work on , production issues of showroom with support, ronn , darren"
    }

    ReplyDelete
  8. @Chris: I'm assuming that you have a @property for the Project*, and that the property is named theProject, just like the instance variable.

    In that case, your json should look like "events":[{"the_project": { ..project stuff.. } ... ]. You can have "theProject" instead of "the_project", and that should work. But you can't have "project". ModelSupport uses the name of the property to decide the JSON dictionary key.

    The relevant test is in ZergSupport/ModelSupport/ZNModelTest.m

    It converts between a model and a Cocoa dictionary, but the same mechanism is used for JSON conversion.

    ReplyDelete
  9. @Chris: is the code working out for you? If not, can you tell me where are you stuck?

    I made the XML parser more versatile, so now it supports nested models as well. So you should be fine no matter what format you use.

    Thanks for making me code this! :)

    ReplyDelete
  10. Hi Victor, I need to use the latest branch in my xcode project. Can I just overwrite the previous one via the command line tool?

    ReplyDelete
  11. @Chris: apologies for not being clear in my blog posts.

    Yes, you can use the same command. I think you can pretty much repeat the initial setup process to update the code.

    I committed all the code in the "submodels" branch.

    I hope it works for you!

    ReplyDelete
  12. Hi Victor,
    Great library, Congrats!
    I have one issue about ZNXmlHttpRequest. Can I use a model with a NSArray of nested models?
    I would like to parse a XML like this

    < top >
    < name >Top Name< /name >
    < finals >
    < final >
    < name >First final< /name >
    < /final >
    < final >
    < name >Second final< /name >
    < /final >
    < final >
    < name >Last final< /name >
    < /final >
    < /finals >
    < /top >

    Thanks

    ReplyDelete
  13. For businesses it needs to stay at the top by using new software solutions. Some applications could be developed by outsource company that provides microsoft application development for business companies.

    ReplyDelete