Oboe.js

This page is also available as a PDF.

The oboe function

Oboe.js exposes only one function, oboe, which is used to instantiate a new Oboe instance. Calling this function starts a new HTTP request unless the caller is managing the stream themselves.

oboe( String url )

oboe({
   url: String,
   method: String,          // optional
   headers: Object,         // optional
   body: String|Object,     // optional
   cached: Boolean,         // optional
   withCredentials: Boolean // optional, browser only
})
Deprecated API
// the doMethod style of calling is deprecated 
// and will be removed in v2.0.0:
oboe.doGet(    url )
oboe.doDelete( url )
oboe.doPost(   url, body )
oboe.doPut(    url, body )
oboe.doPatch(  url, body )

oboe.doGet(    {url:String, headers:Object, cached:Boolean} )
oboe.doDelete( {url:String, headers:Object, cached:Boolean} )
oboe.doPost(   {url:String, headers:Object, cached:Boolean, body:String|Object} )
oboe.doPut(    {url:String, headers:Object, cached:Boolean, body:String|Object} )
oboe.doPatch(  {url:String, headers:Object, cached:Boolean, body:String|Object} )

The method, headers, body, cached, and withCredentials arguments are optional.

  • If method is not given Oboe defaults to GET.
  • If body is given as an object it will be stringified using JSON.stringify prior to sending. The Content-Type request header will automatically be set to text/json unless a different value is explicitly given.
  • If the cached option is given as false cachebusting will be applied by appending _={timestamp} to the URL’s query string. Any other value will be ignored.

Cross-domain requests

Oboe.js is able to make cross-domain requests so long as the server is set up for CORS, for example by setting the Access-Control-Allow-Origin response header.

  • If withCredentials is given as true, cookies and other auth will be propagated to cross-domain requests as per the XHR spec. For this to work the server must be set up to send the Access-Control-Allow-Credentials response header.

Note that Internet Explorer only fully supports CORS from version 10 onwards.

When making requests from Node the withCredentials option is never needed and is ignored. The cors middleware might be useful if you are serving cross-domain requests from Express/Connect.

BYO stream

Under Node.js you may also pass oboe an arbitrary ReadableStream for it to read JSON from. It is your responsibility to initiate the stream and Oboe will not start a new HTTP request on your behalf.

oboe( stream )   // Node.js only

node event

The methods .node() and .on() are used to register interest in particular nodes by providing JSONPath patterns. As the JSON stream is parsed the Oboe instance checks for matches against these patterns and when a matching node is found it emits a node event.

.on('node', pattern, callback)

// 2-argument style .on() ala Node.js EventEmitter#on
.on('node:{pattern}', callback)

.node(pattern, callback)

// register several listeners at once
.node({
   pattern1 : callback1,
   pattern2 : callback2
});

When the callback is notified, the context, this, is the Oboe instance, unless it is bound otherwise. The callback receives three parameters:

node The node that was found in the JSON stream. This can be any valid JSON type - Array, Object, String, Boolean or null
path An array of strings describing the path from the root of the JSON to the matching item. For example, if the match is at (root).foo.bar this array will equal ['foo', 'bar']. Example usage.
ancestors An array of the found item’s ancestors such that ancestors[0] is the JSON root, ancestors[ancestors.length-1] is the parent object, and ancestors[ancestors.length-2] is the grandparent. These ancestors will be as complete as possible given the data which has so far been read from the stream but because Oboe.js is a streaming parser they may not yet have all properties
oboe('friends.json')
   .node('name', function(name){
      console.log('You have a friend called', name);
   });

transforming the JSON

If the callback returns any value, the returned value will be used to replace the parsed JSON node.

See demarshalling to an OOP model and Transforming JSON while it is streaming.

// Replace any object with a name, date of birth, and address 
// in the JSON with a Person instance

.on('node', '{name dob address}', function(personJson){
   return new Person(personJson.name, personJson.dob, personJson.address);
})

oboe.drop

If a node listener returns the special value oboe.drop, the detected node is dropped entirely from the tree.

This can be used to ignore fields that you don’t care about, or to load large JSON without running out of memory.

.on('node', 'person.address', function(address){
   console.log("I don't care what's at", address);
   return oboe.drop;
})

oboe.drop can also be used in a shorthand form:

.on('node', 'person.address', oboe.drop)

Dropping from an array will result in an array with holes in it. This is intentional so that the array indices from the original JSON are preserved:

// json from service is an array of names:
['john', 'wendy', 'frank', 'victoria', 'harry']

oboe('names.json')
   .on('2', oboe.drop)
   .done(console.log)

// this logs:
//    ['john', 'wendy', , 'victoria', 'harry']
//    note the array hole

Meanwhile, dropping from an object leaves no trace of the key/value pair:

// json from service is a hash from names to numbers:
{'john':4, 'wendy': 2, 'frank': 0, 'victoria': 9, 'harry': 2}

oboe('gameScores.json')
   .on('wendy', oboe.drop)
   .done(console.log)

// this logs:
//    {'john':4, 'frank': 0, 'victoria': 9, 'harry': 2}

path event

Path events are identical to node events except that they are emitted as soon as matching paths are found, without waiting for the thing at the path to be revealed.

.on('path', pattern, callback)

// 2-argument style .on() ala Node.js EventEmitter#on
.on('path:{pattern}', callback)

.path(pattern, callback)

// register several listeners at once
.path({
   pattern1 : callback1,
   pattern2 : callback2
});
oboe('friends.json')
   .path('friend', function(name){
      friendCount++;
   });

One use of path events is to start adding elements to an interface before they are complete.

done event

.done(callback)

.on('done', callback)

Done events are fired when the response is complete. The callback is passed the entire parsed JSON.

In most cases it is faster to read the JSON in small parts by listening to node events (see above) than waiting for it to be completely download.

oboe('resource.json')
   .on('done', function(parsedJson){
      console.log('Request complete', parsedJson);
   });

start event

.start(callback)

.on('start', callback)

Start events are fired when Oboe has parsed the status code and the response headers but has not yet received any content from the response body.

The callback receives two parameters:

name type
status Number HTTP status code
headers Object Object of response headers
oboe('resource.json')
   .on('start', function(status, headers){
      console.log('Resource cached for', headers.Age, 'secs');
   });

Under Node.js this event is never fired for BYO streaming.

fail event

   .fail(callback)

   .on('fail', callback)

Fetching a resource could fail for several reasons:

  • Non-2xx status code
  • Connection lost
  • Invalid JSON from the server
  • Error thrown by an event listener

The fail callback receives an object with four fields:

Field Meaning
thrown The error, if one was thrown
statusCode The status code, if the request got that far
body The response body for the error, if any
jsonBody If the server’s error response was JSON, the parsed body
oboe('/content')
   .fail( function( errorReport ){
      if( 404 == errorReport.statusCode ){
         console.error('no such content'); 
      }
   });

.source

Since v.1.15.0

Gives the URL (or stream) that the Oboe instance is fetching from. This is sometimes useful if one handler is being used for several streams.

oboe('http://example.com/names.json')
   .node('name', handleName);

oboe('http://example.com/more_names.json')
   .node('name', handleName);   

function handleName(name){
   console.log('got name', name, 'from', this.source);
}

.header([name])

.header()

.header(name)

.header() returns one or more HTTP response headers. If the name parameter is given that named header will be returned as a String, otherwise all headers are returned as an Object.

undefined wil be returned if the headers have not yet been received. The headers are available anytime after the start event has been emitted. They will always be available from inside a node, path, start or done callback.

.header() always returns undefined for non-HTTP streams.

oboe('data.json')
   .node('id', function(id){
      console.log(   'Server has id', id, 
                     'as of', this.headers('Date'));
   });

.root()

At any time, call .root() on the oboe instance to get the JSON parsed so far. If nothing has yet been received this will return undefined.

var interval;

oboe('resourceUrl')
   .start(function(){
      interval = window.setInterval(function(){
         console.log('downloaded so far:', this.root());
      }.bind(this), 10);
   })
   .done(function(completeJson){
      console.log('download finished:', completeJson);
      window.clearInterval(interval);
   });

.forget()

.node('*', function(){
   this.forget();
})

.forget() is a shortcut for .removeListener() in the case where the listener to be removed is currently executing. Calling .forget() on the Oboe instance from inside a node or path callback de-registers that callback.

// Display only the first ten downloaded items
// but place all in the model 

oboe('/content')
   .node('!.*', function(item, path){
      if( path[0] == 9 )
         this.forget();

      displayItem(item);
   })
   .node('!.*', function(item){   
      addToModel(item);
   });

.removeListener()

.removeListener('node', pattern, callback)
.removeListener('node:{pattern}', pattern, callback)

.removeListener('path', pattern, callback)
.removeListener('path:{pattern}', pattern, callback)

.removeListener('start', callback)
.removeListener('done', callback)
.removeListener('fail', callback)

Remove any listener on the Oboe instance.

From inside node and path callbacks .forget() is usually more convenient because it does not require that the programmer stores a reference to the callback function. However, .removeListener() has the advantage that it may be called from anywhere.

.abort()

Calling .abort() stops an ongoing HTTP call at any time. You are guaranteed not to get any further path or node callbacks, even if the underlying transport has unparsed buffered content. After calling .abort() the done event will not fire.

Under Node.js, if the Oboe instance is reading from a stream that it did not create this method deregisters all listeners but it is the caller’s responsibility to actually terminate the streaming.

// Display the first nine nodes, then hang up 
oboe('/content')
   .node('!.*', function(item){   
      display(item);
   })
   .node('![9]', function(){
      this.abort();
   });

Pattern matching

Oboe’s pattern matching is a variation on JSONPath. It supports these clauses:

Clause Meaning
! Root object
. Path separator
person An element under the key ‘person’
{name email} An element with attributes name and email
* Any element at any name
[2] The second element (of an array)
['foo'] Equivalent to .foo
[*] Equivalent to .*
.. Any number of intermediate nodes (non-greedy)
$ Explicitly specify an intermediate clause in the JSONPath spec the callback should be applied to

The pattern engine supports CSS-4 style node selection using the dollar, $, symbol. See also the example patterns.

Browser support

These browsers have full support:

  • Recent Chrome
  • Recent Firefox
  • Recent Safari
  • Internet Explorer 10

These browsers will run Oboe but not stream:

Unfortunately, IE before version 10 doesn’t provide any convenient way to read an http request while it is in progress. It may be possible to add support for limited streaming in Internet Explorer 8 and 9 using the proprietary XDomainRequest but this object is has a reduced API and is buggy - only GET and POST are supported and it does not allow HTTP headers to be set. It also requires that responses set the Access-Control-Allow-Origin header and fails in IE8 for users browsing using InPrivate mode.

The good news is that in older versions of IE Oboe gracefully degrades. It falls back to waiting for the whole response to return, then fires all the events instantaneously. You don’t get streaming but the responsiveness is no worse than if you had used a non-streaming AJAX download.

Improve this page

Is this page clear enough? Typo-free? Could you improve it?

Please fork the Oboe.js website repo and make a pull request. The markdown source is here.