Jiffy is a fast, lightweight, and reentrant JSON stream parser. Jiffy is extremely portable; it is endian-clean, written in pure ANSI C, and has no external dependencies.
A Jiffy parser uses about 1 kilobyte of memory and never allocates any additional memory, which makes it ideal for embedded systems or any other memory-constrained environment.
Jiffy also includes a binding for the Ruby programming language
(http://ruby-lang.org/). See the "Using Jiffy" section below and the
file ext/ruby/README for additional information.
The Ruby binding also includes a traditional DOM-style JSON encoder and
parser, but the ANSI C version does not. Fortunately, implementing one
on top of a stream parser is fairly straightforward; see the methods
Jiffy.encode and Jiffy.decode in ext/ruby/lib/jiffy.rb for an
example.
Jiffy is distributed under the terms of the MIT/X11 license; please see the "License" section below or the file COPYING for the license terms.
Steps to build Jiffy:
make to compile Jiffy.make install to install Jiffy.You can also statically link Jiffy into your program against the file
src/libjiffy.a, or by copying src/jiffy.c to your source files and
the include/jiffy/ directory to your include directory.
Creating a Jiffy parser is a simple, 4-step process:
jf_init().jf_parse().jf_done().Here's a simple example of a parser callback that prints any integer found in a JSON stream:
static jf_err_t
parse_cb(jf_t *parser, jf_type_t type, const char *val, const size_t len) {
char buf[JF_MAX_BUF_LEN];
/* we only care about integer values */
if (type == JF_TYPE_INTEGER) {
/* copy and null-terminate value */
memcpy(buf, val, len);
buf[len] = '\0';
/* print integer and byte offset */
printf("got integer %s at byte %lu\n", buf, p->num_bytes);
}
/* continue parsing */
return JF_OK;
}
A Jiffy parser callback returns either JF_OK to indicate success, or
JF_STOP to indicate an error. Jiffy passes JF_STOP errors back as
the result from jf_parse(), so you can use JF_STOP in conjunction
with the user_data parameter to perform your own error handling.
Here's a basic main() function that goes with the parser callback
function above:
int main(int argc, char *argv[]) {
char buf[BUFSIZ], err_buf[1024];
size_t len;
jf_t parser;
jf_err_t err;
/* initialize parser and bind it to the parser callback */
jf_init(&parser, (jf_cb_t) parse_cb);
/* read JSON stream from standard input */
while (!feof(stdin) && (len = fread(buf, 1, sizeof(buf), stdin)) > 0) {
/* parse input */
err = jf_parse(&parser, buf, len);
/* check for parsing error */
if (err != JF_OK) {
/* print Jiffy error string to err_buf */
jf_strerror_r(err, err_buf, sizeof(err_buf));
/* print message to standard error and exit */
fprintf(stderr, "ERROR: %s\n", err_buf);
return EXIT_FAILURE;
}
}
/* finish parsing */
err = jf_done(&parser);
/* check for parsing error */
if (err != JF_OK) {
/* print Jiffy error string to err_buf */
jf_strerror_r(err, err_buf, sizeof(err_buf));
/* print message to standard error and exit */
fprintf(stderr, "ERROR: %s\n", err_buf);
return EXIT_FAILURE;
}
/* return success */
return EXIT_SUCCESS;
}
Normally it's better to wrap the error handling in a separate function. Here's the same main function, with error handling moved to a separate function:
/*
* parse_and_check - this function calls jf_parse() and handles
* any errors.
*/
static void
parse_and_check(jf_t *parser, uint8_t *buf, size_t buf_len) {
char err_buf[1024];
jf_err_t err;
if ((err = jf_parse(parser, buf, buf_len)) != JF_OK) {
/* print Jiffy error string to err_buf */
jf_strerror_r(err, err_buf, sizeof(err_buf));
/* print message to standard error and exit */
fprintf(stderr, "ERROR: %s\n", err_buf);
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[]) {
char buf[BUFSIZ];
size_t len;
jf_t parser;
/* initialize parser and bind it to the parser callback */
jf_init(&parser, (jf_cb_t) parse_cb);
/* parse JSON stream from standard input */
while (!feof(stdin) && (len = fread(buf, 1, sizeof(buf), stdin)) > 0)
parse_and_check(&parser, buf, len);
/* finish parsing
*
* (calling jf_parse() with a NULL buffer and zero length is
* equivalent to calling jf_done())
*/
parse_and_check(&parser, 0, 0);
/* return success */
return EXIT_SUCCESS;
}
A slightly more complex example which parses an input stream containing
an array of integers can be found in the file tests/parse_int_array.c.
You can also pass data to the parser callback functions using the
user_data member of the parser context, like so:
/*
* sample data structure to be saved in parser context
*/
typedef struct {
int num_apples,
num_bananas;
} fruit_basket_t;
static jf_err_t
parse_cb(jf_t *parser, jf_type_t type, uint8_t *buf, size_t buf_len) {
/* extract basket structure from parser object */
fruit_basket_t *basket = (fruit_basket_t*) parser->user_data;
/* ... do parsing and stuff here */
return JF_OK;
}
int main(int argc, char *argv[]) {
char buf[BUFSIZ];
size_t len;
jf_t parser;
fruit_basket_t basket;
/* put two apples and one banana in fruit basket */
basket.num_apples = 2;
basket.num_bananas = 1;
/* initialize parser */
jf_init(&parser, parse_cb);
/* save pointer to fruit basket in parser structure */
parser.user_data = (void*) &basket;
/* read JSON stream from standard input */
while (!feof(stdin) && (len = fread(buf, 1, sizeof(buf), stdin)) > 0) {
if ((err = jf_parse(&parser, buf, len)) != JF_OK) {
/* ... handle error */
}
}
if ((err = jf_done(&parser)) != JF_OK) {
/* ... handle error */
}
/* return success */
return EXIT_SUCCESS;
}
Note that the user_data parameter is ignored by Jiffy; you can change
it at any time (including during a parsing callback) and you are
responsible for freeing any resources associated with it.
Jiffy also includes a simple binding for the Ruby programming language (http://ruby-lang.org/). Here's a brief example the Ruby interface:
# load library
require 'jiffy'
# sample json data
DATA = "[1, 2, 3, 4, 5]"
begin
# parse json
Jiffy.parse(DATA) do |type, val|
# determine the type of data
case type
when Jiffy::TYPE_INTEGER
# print integers on the screen
puts "got integer: #{val}"
else
# ignore other types of data
end
end
rescue Jiffy::Error => err
# catch any parsing errors
warn "ERROR: #{err}"
end
You can also pass data in piecemeal, just like the C interface:
# load library
require 'jiffy'
# top-level block to catch any parsing errors
begin
# open input file named 'test_file.json', then
# read it in as 1k-size chunks of data
File.open('test_file.json') do |fh|
#
# create new stream parser
#
# note: you can pass Jiffy::Parser.new a block, and the parser
# will be automatically finalized when the block exits
#
Jiffy::Parser.new do |parser|
# read in file as 1k-sized chunks
while data = fh.read(1024)
# parse chunk
parser.parse(data) do |type, val|
# determine the type of data
case type
when Jiffy::TYPE_INTEGER
# print integers on the screen
puts "got integer: #{val}"
else
# ignore other types of data
end
end
end
end
end
rescue Jiffy::Error => err
# catch any parsing errors
warn "ERROR: #{err}"
end
The Ruby binding also includes a traditional DOM-style JSON encoder and
parser, accessible via the Jiffy.encode and Jiffy.decode methods:
#
# Jiffy.encode example:
#
>> Jiffy.encode({'apples' => 99, 'oranges' => 12, 'need' => %w{pears}, 'tasty' => false})
=> "({\"need\":[\"pears\"],\"tasty\":false,\"apples\":99,\"oranges\":12})"
#
# Jiffy.decode example:
#
>> Jiffy.decode('{"key":[true,false,null,99.6]}')
=> {"key"=>[true, false, nil, 99.6]}
Note that the Ruby binding is not compiled by default, See
ext/ruby/README for installation instructions and additional
documentation.
Copyright (C) 2009 Paul Duncan pabs@pablotron.org
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Paul Duncan (pabs@pablotron.org) http://pablotron.org/