September 23, 2009

Using JESON

JESON
In part 1 of this series, I introduced JESON, a superset of JSON that adds various syntax enhancements. Some may be appalled that I would dare change JSON, but I based my decision on several factors:
  1. JSON's compliance with JavaScript syntax is becoming less an advantage because of the dangers of blindly evaluating JSON source,
  2. I don't use JavaScript, so I could care less about maintaining compatibility with it, and
  3. Since I am mostly writing JESON text, I wanted the format to be as simple and visually appealing as possible.
The enhancements in JESON are summarized again as:
  1. keys in dictionaries do not need quotes if the word is all alphanumeric characters + '_'
  2. single quotes are interchangeable with double quotes
  3. the '=' character is interchangeable with the ':' character
  4. the ':' or '=' characters are optional
  5. Outer object is assumed to be a dictionary if '{' or '[' does not start the text
  6. "()" characters are interchangeable with "{}"
  7. commas separating array items and dictionary key/value pairs are optional
  8. comments via "//" and "/**/" are supported
For my JESON implementation, I modified Stig Brautaset's json-framework, version 2.2.1. I mostly changed the parsing routines. I also rearranged some of the files and renamed classes to avoid confusion. Kudos to Stig for his wonderful JSON work. The JESON parser code and an example application can be fetched from the Osmorphis group. Currently this is an iPhone XCode project, but the JESON parser code should also be usable on the Mac. However, since I don't develop in Objective-C on the Mac, I cannot vouch for that. The project creates a static library, one that is usable in iPhone applications.

Including the JESON library

To use the JESON library in your project, XCode seems to make the process a bit convoluted. Perhaps there are easier ways, but I follow the instructions given here at Coding Ventures. Be sure to change your "search for headers" setting to include ../jeson/JESON, which is where the JESON header files are located.

Accessing the JESON code

To access the JESON definitions in your code, you must import the header file: #import "JESON.h"

Parsing JESON

The primary use for JESON package is to parse it JESON text. JESON text might be in a file, inside your code, or come from the web. Where it comes from is of no concern to the parser. The JESON parser only parses strings, so wherever it comes from, you have to get it into an NSString object. There are a couple approaches you can take to parse JESON text. The first way is to directly call the JESON parser: JESON* parser = [JESON new];
id root = [parser parseString:myJESONText];
[parser release]; What is put into root depends on the JESON text. If the topmost container is a dictionary, then root will be an NSDictionary. If it is an array, then root will be an NSArray. As mentioned in part 1, JESON does not support JESON fragments. The topmost object will be either an array or a dictionary. If there was an error parsing the JESON text, then parserString: returns nil. In order to get the actual error, look at the error property of the parser: if(root == nil) {
NSLog("Error parsing JESON: %@", [parser.error localizedDescription]);
} The second way of parsing JESON is via a category extension made to NSString. If you have your JESON text in a NSString object, then simply call the parseAsJESON method to return the root object: NSString* myJESONText;
// ....
id root = [myJESONText parseAsJESON]; Writing JESON JSON can also be written given a dictionary or an array. Obviously, the objects inside these containers must be compatible with JSON/JESON. Currently, the JESON package does not create JESON syntax; it creates pure JSON. This is a low priority for me at the moment, since I don't have a need to reverse the parse operation and actually write JESON. This will be added later. In the meantime, the write code is essentially the same as what Stig has in his original json-framework package. NSMutableDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:...];
JESON* writer = [JESON new];
NSString* JESONText = [writer createFromObject:dict];
[writer release];

Some Examples

The following are some code snippets from the TableFormEditor package which is currently being enhanced to allow configuration via JESON/JSON. Since the TableFormEditor package will accept a dictionary of configuration information, you could also use a plist if you so desire, but I find JESON much more to my liking. As you can see below, it's not a bad format to enter this type of information. Here is an example of how to configure a particular TableFormEditor instance.

title = 'Person Record'
showLabels = true
allowDelete = true
labels { delete = "Delete Record", save = "Save", cancel = "Cancel" }
firstFocusRow = 1

fields [
# fields will be displayed in this order
{
  name = "Name"
  textfield {
     adjustsFontSizeToFitWidth = true
     minimumFontSize = 7.0
     autocorrectionType = 'No'
     autocapitalizationType = 'Words'
     text= ''
  }
}

{
 name = "Age"
 textfield {
   keyboardType = 'NumberPad'
   text = 23
 }
}

{
  name = "Homepage"
  textfield {
    // can also use the longer enum name
    keyboardType = 'UIKeyboardTypeURL'
    autocorrectionType = 'UITextAutocorrectionTypeNo'
    autocapitalizationType = 'UITextAutocapitalizationTypeNone'
    // some enums can use booleans
    clearButtonMode = true
    text = ''
  }
}

{
 name = "Password"
 textfield {
   secureTextEntry = true
   text = ''
 }
}
]

I will discuss the new TableFormEditor in another post; the example above just shows the type of input that is expected. The new initializers for the TableFormEditor class now take a template of type NSDictionary*. The template is the above data, parsed of course. The following code is what reads in the above text from a file and parses it into a NSDictionary root:
// read the JESON file in
NSDictionary* templ;
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"formTemplate" ofType:@"txt"];
if (filePath) {
NSString *myText = [NSString stringWithContentsOfFile:filePath];
templ = (NSDictionary*) [myText parseAsJESON];
}
This root is then passed to the TableFormEditor initializer. The initializer then initializes itself based on the key/values above. Before, you had to do this via properties in code. Now it can be done in a config file. Here is a snippet of code that looks at the first 5 settings:
// get values from template
if(templ) {
id obj;
if(obj = [templ objectForKey:@"title"]) {
   self.title = obj;
}
if(obj = [templ objectForKey:@"showLabels"]) {
   self.showLabels = [obj boolValue];
}
if(obj = [templ objectForKey:@"labels"]) {
   NSDictionary* l = obj;
   if(obj = [l objectForKey:@"save"]) {
       self.saveButtonLabel = obj;
   }
   if(obj = [l objectForKey:@"cancel"]) {
       self.cancelButtonLabel = obj;
   }
   if(obj = [l objectForKey:@"delete"]) {
       self.deleteButtonLabel = obj;
   }
}
if(obj = [templ objectForKey:@"allowDelete"]) {
   self.allowDelete = [obj boolValue];
}
if(obj = [templ objectForKey:@"firstFocusRow"]) {
   self.firstFocusRow = [obj intValue];
}
}
As you can see, there is still a fair bit of parsing to do, but this is only written once. The clients of the TableFormEditor just write config files in JESON (or a plist if that's their desire). In future articles I'll go into more detail of how to use the new TableFormEditor package, and I'll discuss scripting possibilities with JESON.

No comments:

Post a Comment