Jastor – Translate JSON/NSDictionary to Typed Objective-C Classes

Posted in iOS, iPhone, Objective-C

Jastor on Github ]

Jastor is an Objective-C base class that is initialized with a dictionary (probably from your JSON response), and assigns dictionary values to all its (derived class’s) typed @properties.

It supports nested types, arrays, NSString, NSNumber, NSDate and more.

Jastor is NOT a JSON parser. For that, you have JSONKit, yajl and many others.

The name sounds like JSON to Objecter. Or something.

Examples

You have the following JSON:

{
    "name": "Foo",
    "amount": 13
}

and the following class:

@interface Product
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSNumber *amount;
@end
@implementation Product
@synthesize name, amount;
@end

with Jastor, you can just inherit from Jastor class, and use initWithDictionary:

// Product.h
@interface Product : Jastor
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSNumber *amount;
@end
// Product.m
@implementation Product
@synthesize name, amount;
@end
// Some other code
NSDictionary *dictionary = /* parse the JSON response to a dictionary */;
Product *product = [[Product alloc] initWithDictionary:dictionary];
// Log
product.name // > Foo
product.amount // > 13

Nested Objects

Jastor also converts nested objects to their destination type:

// JSON
{
    "name": "Foo",
    "category": {
        "name": "Bar Category"
    }
}
// ProductCategory.h
@interface ProductCategory : Jastor
@property (nonatomic, copy) NSString *name;
@end
// ProductCategory.m
@implementation ProductCategory
@synthesize name;
@end
// Product.h
@interface Product : Jastor
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) ProductCategory *category;
@end
// Product.m
@implementation Product
@synthesize name, category;
@end
// Code
NSDictionary *dictionary = /* parse the JSON response to a dictionary */;
Product *product = [[Product alloc] initWithDictionary:dictionary];
// Log
product.name // > Foo
product.category // >
product.category.name // > Bar Category

Arrays

Having fun so far?

Jastor also supports arrays of a certain type:

// JSON
{
    "name": "Foo",
    "categories": [
        { "name": "Bar Category 1" },
        { "name": "Bar Category 2" },
        { "name": "Bar Category 3" }
    ]
}
// ProductCategory.h
@interface ProductCategory : Jastor
@property (nonatomic, copy) NSString *name;
@end
// ProductCategory.m
@implementation ProductCategory
@synthesize name;
@end
// Product.h
@interface Product : Jastor
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) NSArray *categories;
@end
// Product.m
@implementation Product
@synthesize name, categories;
+ (Class)categories_class {
    return [ProductCategory class];
}
@end
// Code
NSDictionary *dictionary = /* parse the JSON response to a dictionary */;
Product *product = [[Product alloc] initWithDictionary:dictionary];
// Log
product.name // > Foo
product.categories // >
[product.categories count] // > 3
[product.categories objectAtIndex:1] // >
[[product.categories objectAtIndex:1] name] // > Bar Category 2

Notice the declaration of

+ (Class)categories_class {
    return [ProductCategory class];
}

it tells Jastor what class of items the array holds.

Nested + Arrays = Trees

Jastor can handle trees of data:

// JSON
{
    "name": "1",
    "children": [
        { "name": "1.1" },
        { "name": "1.2",
          children: [
            { "name": "1.2.1",
              children: [
                { "name": "1.2.1.1" },
                { "name": "1.2.1.2" },
              ]
            },
            { "name": "1.2.2" },
          ]
        },
        { "name": "1.3" }
    ]
}
// ProductCategory.h
@interface ProductCategory : Jastor
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) NSArray *children;
@end
// ProductCategory.m
@implementation ProductCategory
@synthesize name, children;
+ (Class)children_class {
    return [ProductCategory class];
}
@end
// Code
NSDictionary *dictionary = /* parse the JSON response to a dictionary */;
ProductCategory *category = [[ProductCategory alloc] initWithDictionary:dictionary];
// Log
category.name // > 1
category.children // >
[category.children count] // > 3
[category.children objectAtIndex:1] // >
[[category.categories objectAtIndex:1] name] // > 1.2
[[[category.children objectAtIndex:1] children] objectAtIndex:0] // >
[[[[category.children objectAtIndex:1] children] objectAtIndex:0] name] // > 1.2.1.2

How does it work?

Runtime API. The class’s properties are read in runtime and assigns all values from dictionary to these properties with NSObject setValue:forKey:. For Dictionaries, Jastor instantiates a new class, based on the property type, and issues another initWithDictionary. Arrays are only a list of items such as strings (which are not converted) or dictionaries (which are treated the same as other dictionaries).

Installation

Clone Jastor from github: https://github.com/elado/jastor

Copy Jastor.m+.h and JastorRuntimeHelper.m+.h to your project, create a class, inherit, use the initWithDictionary and enjoy!

REALLY Good to know

Where’s the dealloc in the inheritor classes?

dealloc is implemented in the base class and it nilifies all properties.

What about properties that are reserved words?

As for now, id is converted to objectId automatically. Maybe someday Jastor will have ability to map server and obj-c fields.

Jastor classes also conforms to NSCoding protocol

So you get initWithCoder/encodeWithCoder for free.

You can look at the tests for real samples.

Convert Ruby Hash Syntax to 1.9 (:sym => value to sym: value) On Files

After complaining to our developers about using the old => syntax in hashes, I was challenged by my ninja colleague Avi Tzurel to create a script that will convert all the old Ruby hash syntax to the new 1.9, beautiful colon notation.

I came up with this:

Dir['**/*.rb'].each { |f|
  s = open(f).read
  awesome_rx = /(?/
  count = s.scan(awesome_rx).length
  next if count.zero?
  s.gsub!(awesome_rx, '\1\2:')
  puts "#{count} replacements @ #{f}"
  open(f, 'w') { |b| b << s }
}

The crazy negative lookbehinds ensures that nothing like

 begin   # ... rescue Namespace::Exception => ex
end
def something
  return :x => 1, :y => 2
end

will get replaced. Turns out that after return you can only use the old syntax, or have curly brackets. Also, unfortunately, negative lookbehinds can’t contain Regexp inside them. So the multiple negative lookbehinds are like a big ‘or’.

Our really big project, with hundreds of .rb files, Gogobot, converted and is working great. Still afraid to do the final git push though ;)

Enjoy!

Isotope – Hybrid Ruby on Rails Template Engine for Client Side and Server Side (Updated)

Posted in Ruby, Ruby on Rails

Isotope on Github ]

Update: Jul 23th, 2011

Updated to Ruby 1.9.2 with ArthurN‘s amazing fork.

Update: Apr 13th, 2011

Updated API, external script support and fixes.

The problem:

In Ajax-based sites, there’s a constant dilemma: How to get objects rendered in templates? In server side (and output full HTML)? Client side (and mess with JSON objects and HTML strings/DOM generation)?
What should be the role division between server and client?

Common Approaches, Pros & Cons:

A few approaches to output a rendered template evaluated with an object are:

Approach #1: Regular ERB Partial

Evaluate a simple ERB partial with a local object, and server it as a string to the client, simply by

<%= render :partial => "article", :object => @article %>

in a view, or from a controller and request it by Ajax.

This partial can look like:

<h2><%= article.title %></h2>
<div class="content">
<%= article.content %>
</div>
<ul class="tags">
    <% article.tags.each { |tag| %>
    <li><%= tag.name %></li>
    <% } %>
</ul>
Pros
  • Simple, readable and well known ERB for Rails/Sinatra
  • SEO and accessibility – HTML code is downloaded into the source
Cons
  • Server side only
  • Requires download of the whole HTML code, can cause performance issues
  • Cannot bind easily to a different object on client side. Must re-rendered in the server-side and be downloaded

Approach #2: Client Side EJS Template with JSON Objects

Having an EJS template in the HTML code, with techniques such as John Resig’s JavaScript Micro-Templating

<script type="text/html" id="article-template">
<h2><%= item.title %></h2>
<div class="content">
<%= item.content %>
</div>
<ul class="tags">
    <% item.tags.forEach(function (tag) { %>
    <li><%= tag.name %></li>
    <% }); %>
</ul>
</script>

Query the server for a JSON article and evaluate the template with this object into a string, and place it inside a container, using a technique as mentioned:

var results = document.getElementById("results"); // some container on the page
results.innerHTML = tmpl("article-template", article); // article is an object, probably a result of an AJAX JSON request
Pros
  • Fast – requires the server to send only the JSON object and the HTML is downloaded only once as a template
Cons
  • SEO and accessibility – HTML code isn’t in the source of the page but being rendered after load

Approach #3: Regular ERB Partial With Marked Spots for Data Place Holders

This approach tries to combine server side and client side but requires a lot of work. It contains a regular ERB template and place holder markers like class names on elements.
The template can be first evaluated on the server with a Ruby object and on the client side it can be evaluated with a different JS object (probably from a JSON request).

Template file should look like:

<h2 class="data-title"><%= article.title %></h2>
<div class="content data-content">
<%= article.content %>
</div>
<ul class="tags data-tags">
    <% article.tags.each { |tag| %>
    <li><%= tag.name %></li>
    <% } %>
</ul>

And then, from a JS function, doing something like:

// article is an object probably from a JSON request
container.querySelector('.data-title').textContent=article.title;
container.querySelector('.data-content').textContent=article.content;
var tags=container.querySelector('.data-tags');
tags.innerHTML="";
article.tags.forEach(function (tag) {
    tags.appendChild(document.createElement("li")).textContent=tag.name;
});
Pros
  • One template for both client and server (not really, see Cons)
  • SEO and accessibility – HTML code is downloaded into the source
Cons
  • On the client side – Must mimic the Ruby functionality with JS when it comes to loops, conditions etc. However, text values are pretty easy to embed. This code should probably written manually for everything that is not a simple textual content.
  • Must maintain data place holder markers

So…

In these three approaches, the developer needs to choose according to the task and the project requirements, or worse, maintain two templates, ERB and EJS.

Each approach is written in a totally different way, and switching between the approaches means a lot of work.

Introducing: Isotope – Ruby Hybrid Template Engine for Client Side and Server Side

So why not combining all the pros of the approaches together?

The biggest constraints to be considered are:

  • Client side doesn’t understand Ruby
  • Ruby can’t be translated fully into JavaScript
  • And the most important one: Template should be maintained in one single file for both client and server uses

Isotope is from greek – “Equal Place”. An equal place of editing a template for both client and server (Thanks @yuvalraz for the name!).

Using jbarnette‘s AWESOME Johnson gem, Ruby and JavaScript can interact together!

That means, that ruby code can handle EJS templates and JSON objects. A great and very inspiring article is Write your Rails view in… JavaScript? by Aaron Patterson.

In this approach, only one template is written and maintained in an EJS format, for both client side and server side.

Usage

Isotope gives the ability to have a single template file, and easily switch between the approaches:

# article.html.ejs
<h2><%= item.title %></h2>
<div class="content">
<%= item.content %>
</div>
<ul class="tags">
    <% item.tags.forEach(function (tag) { %>
    <li><%= tag.name %></li>
    <% }); %>
</ul>

On the Client Side

Outputting from the server side (controller or view)

<%= isotope_render_template("articles/article", :id => "article-template") %>

The above code will output:

<script type="text/x-isotope" id="article-template">
<h2><%= item.title %></h2>
<div class="content">
<%= item.content %>
</div>
<ul class="tags">
    <% item.tags.forEach(function (tag) { %>
    <li><%= tag.name %></li>
    <% }); %>
</ul>
</script>

which is easy to evaluate with any JS object using the mentioned technique.

On the Server Side (The Holy Grail)

Using Johnson, the famous micro-templating technique and JSONed Ruby objects, this library provides the following functionality:

<%= isotope_render_partial("articles/article", :locals => { :item => @article }) %>

This code reads the source of the EJS file, uses Johnson and John Resig’s technique and serves a string as an output.

Configuration

External JS files can be included in order to have special functionality in the EJS templates, on both client and server.
To include files, create a config/isotope.yml file and fill this array, relatively to the app root:

include_scripts:
    - /public/javascripts/isotope_functions.js

And in order to have it available on client side, put this line before the <body> close:

<%= isotope_included_scripts.html_safe %>

Scripts will be automatically included in server side.

Scripts and templates are never cached on development environment.

Installation:

# Rails 3.x
ruby script/rails plugin install git@github.com:elado/isotope.git
# Rails 2.3.x
ruby script/plugin install git@github.com:elado/isotope.git
Rails
Rails 3.x

Add to your Gemfile

gem 'json'
gem 'johnson'
gem 'isotope'

and run bundle install

Rails 2.3.x

Add to config/environment.rb

config.gem 'json'
config.gem 'johnson'
config.gem 'isotope'

and run rake gems:install

Add to config/environment.rb

require ‘isotope’

Server Side Example:
# ArticlesController
def show
    @article = {
        :title => "Hello!",
        :content => "World!",
        :tags => [
            {:name => "tag 1"},
            {:name => "tag 2"},
            {:name => "tag 3"},
            {:name => "tag 4"}
        ]
    } # Or an ActiveRecord fetch
    render :text => isotope_render_partial('articles/article', :locals => { :item => @article })
end

Or, with a view:

# ArticlesController
def show
    @article = {
        :title => "Hello!",
        :content => "World!",
        :tags => [
            {:name => "tag 1"},
            {:name => "tag 2"},
            {:name => "tag 3"},
            {:name => "tag 4"}
        ]
    } # Or an ActiveRecord fetch
end
# views/articles/show.html.erb
<%= isotope_render_partial('articles/article', :locals => { :item => @article }) %>
Client Side Example:
# views/articles/show.html.erb
<%= isotope_render_template('articles/article', :id => "article") %>
Sinatra

Actually the same usage, more or less.

Run Tests

Install rspec (gem install rspec)

rspec test/isotope_spec.rb

Launch sample Rails app

cd examples/rails3-example && rails s

and go to http://localhost:3000

Development Tip: Realtime Code Backup with Dropbox

Posted in Productivity

Backups are important. But usually they are made daily or even less often than this.

Quick tip I wish I had known a month ago when my MacBook froze and didn’t boot after a fruitful, productive day: backup all the code in realtime, all the time, with Dropbox.

Every CMD+S sends the file to Dropbox, and that means, that if something terrible happens to my computer (can’t boot, coffee accident, or it gets stolen), I know that all my code is backed up to the latest save.

That doesn’t even mean you should put your code directory inside the Dropbox’s one, you can Symlink it, excluding big files that don’t need to be backed up, or if you’re short on space.

A template I use for Rails projects w/ Git, excluding the tmp, log and other irrelevant folders:

mkdir -p ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/.git ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/.gitignore ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/.rvmrc ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/app ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/Capfile ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/config ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/config.ru ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/db ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/doc ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/features ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/Gemfile ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/Gemfile.lock ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/lib ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/public ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/Rakefile ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/README ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/script ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/spec ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/test ~/Dropbox/Sites/PROJECT_NAME/
ln -s ~/Sites/PROJECT_NAME/vendor ~/Dropbox/Sites/PROJECT_NAME/

If you still don’t have a Dropbox account, you can use my referral link: Create Dropbox account or create a new account without my referral here.

Tree Rendering in Client-Side, No Recursion

Posted in DOM, JavaScript

I once built a nested message board (using ASP & VBScript, bah). In order to print all messages, nested, I created a recursive function that queries the DB for all messages of a certain parent, prints the messages and calls the same function itself with parent ID of the current message. That was a disaster to the server, as a single page could execute about 100 queries.

I decided to move the rendering logic to the client side, since SEO wasn’t such a big of a deal for this, and the load time was critical. So at first I queried the messages I wanted (I could do this in one query thanks to the DB structure), I pushed the data to JavaScript, as a flat, unordered array of objects, using JSON:

var treeData=[
	{ "id":1,	"parentId":null,  "title":"1"			},
	{ "id":2,	"parentId":1,     "title":"1.1"			},
	{ "id":3,	"parentId":null,  "title":"2"			},
	{ "id":4,	"parentId":1,     "title":"1.2"			},
	{ "id":5,	"parentId":1,     "title":"1.3"			},
	{ "id":6,	"parentId":4,     "title":"1.2.1"		},
	{ "id":7,	"parentId":4,     "title":"1.2.2"		},
	{ "id":8,	"parentId":4,     "title":"1.2.3"		},
	{ "id":9,	"parentId":null,  "title":"3"			},
	{ "id":10,	"parentId":9,     "title":"3.1"			},
	{ "id":11,	"parentId":10,    "title":"3.1.1"		},
	{ "id":12,	"parentId":11,    "title":"3.1.1.1"		},
	{ "id":13,	"parentId":11,    "title":"3.1.1.2"		},
	{ "id":14,	"parentId":11,    "title":"3.1.1.3"		},
	{ "id":15,	"parentId":13,    "title":"3.1.1.2.1"	}
];

The first approach to print this, nested, is using a recursive function in JavaScript, that, on every call needs to find the child messages based on their parentId, and actually mimic the server behavior (loop on root messages, query child messages, print, recursively).

But a way cooler approach, is to utilize the DOM itself for nesting, and to utilize a hash table indexer to store and find messages based on their id.

After creating all elements for all messages, a hash table (associative array, or just a JavaScript “object”) can be used to store a node based on its id, so when we want to find it, we can just use nodesById[someNodeId]:

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

Then, the magic of having all elements nested happens when looping on all nodes. Each node has a parent_id except root nodes. We need to find the parent of current node in the nodesById object, by using nodesById[nodeParentId], and append the current node to its parent we found. Root nodes appended to a root container.

for (var i=0;i<nodeCollection.length;i++) {
    var node=nodeCollection[i];
    var parentElement=node.data.parentId ? nodesById[node.data.parentId].getChildrenElement() : rootNodeItemsContainer;
    parentElement.appendChild(node.element);
}

That way, we utilize the DOM for native nesting, without a recursion, and a simple single loop. The array doesn’t even have to be ordered, and no worries about requesting a node when its parent isn’t exist, as all of the elements are already created before.

The Entire Code

var treeData=[
	{"id":1,"parentId":null,"title":"1"},
	{"id":2,"parentId":1,"title":"1.1"},
	{"id":3,"parentId":null,"title":"2"},
	{"id":4,"parentId":1,"title":"1.2"},
	{"id":5,"parentId":1,"title":"1.3"},
	{"id":6,"parentId":4,"title":"1.2.1"},
	{"id":7,"parentId":4,"title":"1.2.2"},
	{"id":8,"parentId":4,"title":"1.2.3"},
	{"id":9,"parentId":null,"title":"3"},
	{"id":10,"parentId":9,"title":"3.1"},
	{"id":11,"parentId":10,"title":"3.1.1"},
	{"id":12,"parentId":11,"title":"3.1.1.1"},
	{"id":13,"parentId":11,"title":"3.1.1.2"},
	{"id":14,"parentId":11,"title":"3.1.1.3"},
	{"id":15,"parentId":13,"title":"3.1.1.2.1"}
];
function NodeItem(data) {
	this.data=data;
	var container=document.createElement("li"),
		title=container.appendChild(document.createElement("div"));
	title.innerHTML=this.data.title;
	this.element=container;
} NodeItem.prototype={
	// lazy children element creation - not all nodes have children
	getChildrenElement:function () {
		return this._childElement = this._childElement || this.element.appendChild(document.createElement("ul"));
	}
};
// convert all nodes to NodeItem instance
var nodeCollection=treeData.map(function (node) { return new NodeItem(node); });
// for faster lookup, store all nodes by their id
var nodesById={};
for (var i=0;i<nodeCollection.length;i++) nodesById[nodeCollection[i].data.id]=nodeCollection[i];
var rootNodeItemsContainer=document.createElement("ul");
// the magic happens here:
// every node finds its parent (by the id), and it's being adopted by the parent's children element
// that, actually, builds the tree, before it's in the document
// all root nodes are appended to a root container which is appended to an element on the document
for (var i=0;i<nodeCollection.length;i++) {
	var node=nodeCollection[i];
	var parentElement=node.data.parentId ? nodesById[node.data.parentId].getChildrenElement() : rootNodeItemsContainer;
	parentElement.appendChild(node.element);
}
document.body.appendChild(rootNodeItemsContainer);



Page optimized by WP Minify WordPress Plugin