cson  Update of "HowTo"

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview

Artifact ID: 3a98a7088c8eea3eac6025c6ed023af74f97e353
Page Name:HowTo
Date: 2011-05-09 17:58:45
Original User: stephan
Parent: eb4c4b9b28acafee74b11e5869be3f6b1687af08
Content

ACHTUNG: THIS PAGE IS NOW MAINTAINED IN THE NEW WIKI: http://whiki.wanderinghorse.net/wikis/cson/?page=HowTo

See also: TipsAndTricks

How to use cson

This page demonstrates just about everything one needs to know in order to use the library. Full details of the types and functions shown here are in the API docs (in cson.h).

See also: cson_sqlite3

Compiling

The build tree comes with a GNU Makefile. If you have a different build environment, the files you will need are:

  • include/wh/cson/cson.h (public API and docs)
  • cson.c (the core library implementation)
  • cson_lists.h (utility code used internally by the core library)
  • parser/JSON_parser.{c,h} (the underlying input parser)

The test.c and json-parser.c programs demonstrate how to use it.

Alternately (and more simply), you can use the amalgamation build.

It should compile cleanly on 32- and 64-bit platforms in C89 mode.

It has no 3rd-party dependencies other than the underlying JSON_parser code, which is included in the source tree. The JSON_parser code is an implementation detail, not part of the public API. The point is: rely only on the documented public API in cson.h.

API Docs

The API docs are in cson.h, and doxygen can be used to process them:

~> cd doc
~> make doc

Generating JSON

JSON is typically generated by populating a root-level object or array with values, as shown here.

// Create a root object:
cson_value * objV = cson_value_new_object();
// Objects, Arrays, and Strings are represented by higher-level
// objects owned by their containing cson_value, so we fetch
// that Object:
cson_object * obj = cson_value_get_object(objV);
// Achuntg: destroying objV will also invalidate/destroy obj.

// Add some values to it:
cson_object_set( obj, "myInt", cson_value_new_integer(42) );
cson_object_set( obj, "myDouble", cson_value_new_double(42.24) );

// Add an array:
cson_value * arV = cson_value_new_array();
cson_object_set( obj, "myArray", arV ); // transfers ownership of arV to obj
cson_array * ar = cson_value_get_array(arV);
cson_array_set( ar, 0, cson_new_value_string("Hi, world!", 10) );

// Output it to stdout:
cson_output_FILE( objV, stdout, NULL );

// Free up the root and all values it owns:
cson_value_free( objV );

ACHTUNG ACHTUNG ACHTUNG: it is critical that array/object values do not form cycles anywhere (whether across containers or within the same container). In other words, there may be absolutely no circular references. If there are, the results will be leaks and/or corruption and/or crashes due to double-free()s.

As of 20110325 it is legal for a given value instance to be inserted into multiple containers or into the same container multiple times, provided that no cycles are introduced (see above). The library internally reference-counts the values and they will be cleaned up when the last container holding that value is cleaned up.

Outputing to a string buffer

To output your JSON objects to a string buffer instead of a FILE:

cson_buffer buf = cson_buffer_empty;
int rc = cson_output_buffer( objV, &buf, NULL );
if( 0 != rc ) { ... error ... }
else {
   JSON data is the first (buf.used) bytes of (buf.mem).
}
// Regardless of success or failure, make sure to either
// clean up the buffer:
{
  cson_buffer_reserve( &buf, 0 ):
}
// or take over ownership of its bytes:
{
   char * mem = (char *)buf.mem;
   // mem is (buf.capacity) bytes long, of which (buf.used)
   // are "used" (they contain the JSON data in this case).
   buf = cson_buffer_empty;
   ... you now own the buffer's memory and must eventually free() it ...
}

Changing Formatting Options

The cson_output() family of functions all take an optional cson_output_opt argument which can be use like this:

cson_output_opt opt = cson_output_opt_empty;
opt.addNewline = 1; // boolean: add a newline to the final output?
opt.maxDepth = 15; // will error out if the tree is deeper than this
opt.indentation = 1; //(0)=off, (1)=1 tab/level, (>1)=that many spaces/level
cson_output_FILE( myValue, stdout, &opt );

Reading JSON

It's pretty easy: select an input source and tell cson_parse() to use it...

cson_value * root = NULL;
int rc = cson_parse_FILE( &root, stdin, NULL, NULL );
// The NULL arguments hold optional information for/about
// the parse results. These can be used to set certain
// parsing options and get more detailed error information
// if parsing fails.

if( 0 != rc ) {
   printf("Error code %d (%s)!\n", rc, cson_rc_string(rc) );
   return ...;
}

if( cson_value_is_object(root) ) {
    cson_object * obj = cson_value_get_object(root);
    ...
}
else if( cson_value_is_array(root) ) {
    cson_array * ar = cson_value_get_array(root);
    ...
}
else {
   // "Cannot happen" because parse would fail in this case.
}

// Clean up:
cson_value_free(root);

You can use cson_parse_string() to read JSON from string. See below for an example of creating your own input source.

The Object and Array APIs provide mechanism for adding, fetching, and traversing the child elements.

Traversing and fetching values from JSON Arrays and Objects

Here are examples of how to fetch and traverse values from Arrays and Objects:

For the following examples, assume that obj is a populated cson_object and ar is a populated cson_array.

Fetch a single array element:

cson_value * v = cson_array_get( ar, 1 /* array index */ );

Iterate over array:

unsigned int len = cson_array_length_get(ar);
unsigned int i;
for( i = 0; i < len; ++i ) {
  v = cson_array_get( ar, i );
  ...
}

Fetch single property from an object:

cson_value * v = cson_object_get( obj, "myKey" );

Fetch single nested property from an object:

// Search for value in an object structured like this:
//    { "subobj": { "sub2obj": { "myKey": whatever } } }
cson_value * v = cson_object_get_sub( obj, "subobj.subobj2.myKey", '.' );

Iterate over object entries:

cson_object_iterator iter;
int rc = cson_object_iter_init( obj, &iter );
if( 0 != rc ) { error, but can only fail if obj is NULL }
cson_kvp * kvp; // key/value pair
while( (kvp = cson_object_iter_next(&iter)) )
{
    cson_string const * ckey = cson_kvp_key(kvp);
    cson_value * v = cson_kvp_value(kvp);
    ... use ckey and v ...
    // Here we just print out: KEY=VALUE
    fprintf( stdout, "%s", cson_string_cstr(ckey) );
    putchar('=');
    cson_output_FILE( v, stdout, NULL );
}
// cson_object_iterator objects own no memory and need not be cleaned up.
// Iteration results are undefined if the object being iterated over is
// modified (keys added/removed) while iterating.

Using a custom input source

cson_parse() is the generic front-end to parsing input. It takes parameters which tell it where to pull its data, where to store the result object/array, and some optional settings to tweak how the parser works. That function is driven by a generic callback interface which clients may implement in order to stream data from arbitrary sources. A couple implementations are provided, and adding new ones is really easy.

The following example demonstrates how to create a custom input source handler, so that cson_parse() can read from wherever you need it to. This code actually comes from the core library (which explains why it has so much error checking) and implements reading of JSON from a client-supplied C string.

/** Internal type to hold state for a JSON input string.
 */
typedef struct
{
    /* Start of input string. */
    char const * str;
    /* Current iteration position. Must initially be == str. */
    char const * pos;
    /* Logical EOF, one-past-the-end of str. */
    char const * end;
}  StringSource_t;

/**
   A cson_data_source_f() implementation which requires the state argument
   to be a properly populated (StringSource_t*).
*/
static int cson_data_source_StringSource( void * state, void * dest,
                                          unsigned int * n )
{
    StringSource_t * ss = (StringSource_t*) state;
    unsigned int i;
    unsigned char * tgt = (unsigned char *)dest;
    if( ! ss || ! n || !dest ) return cson_rc.ArgError;
    else if( !*n ) return 0 /*ignore it or return cson_rc.RangeError*/;
    /* Copy the ss data, starting at the current cursor position,
       to dest. We update the ss state so that the next call to this
       function (from cson_parse(), which collects its input by looping
       over this function) will know its cursor position.
    */
    for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt )
    {
        *tgt = *ss->pos;
    }
    *n = i /* Tells cson_parse() how many bytes we copied.
              If this is less than the initial value of *n then
              EOF is assumed.
           */;
    return 0;

}

/**
 Convenience wrapper around cson_parse() which uses the first len
 bytes of the given string as JSON.
*/
int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len,
                       cson_parse_opt const * opt, cson_parse_info * info )
{
    if( ! tgt || !src ) return cson_rc.ArgError;
    else if( !*src || (len<2 /*2==len of {} and []*/) )
         return cson_rc.RangeError;
   /* We could optionally do a quick check of *src for '{', '[', or spaces,
      and fail early if those are not found.
    */
    else
    {
        StringSource_t ss;
        ss.str = ss.pos = src;
        ss.end = src + len;
        return cson_parse( tgt, StringSource, &ss, opt, info );
    }

}

Using a custom output destination

Like cson_parse(), cson_output() also takes a callback which allows it to stream its output to an arbitrary destination.

The whole implementation for the FILE-handling output API looks something like this:

/* cson_data_dest_f() impl which requires that state be a
   writable (FILE*).
*/
int cson_data_dest_FILE( void * state, void const * src, unsigned int n )
{
    if( ! state ) return cson_rc.ArgError;
    else if( !src || !n ) return 0;
    else
    {
        FILE * f = (FILE*) state;
        size_t const wsz = fwrite( src, n, 1, f );
        return (1 == wsz) ? 0 : cson_rc.IOError;
    }
}
/* Convenience/type-safety wrapper around cson_output().

   Pedantic note: this example is not quite equivalent to the real
   cson_output_FILE() implementation: that one uses different default
   options if (fmt==NULL).
*/
int cson_output_FILE( cson_value const * src, FILE * dest,
                      cson_format_opt const * fmt )
{
    return cson_output( src, cson_data_dest_FILE, dest, fmt );
}

And it can be called like:

cson_output_FILE( myValue, stdout, NULL /* = use default options */ );

// or...

cson_output_opt opt = cson_output_opt_empty;
opt.addNewline = 1;
opt.maxDepth = 15; // will error out if the tree is deeper than this
opt.indentation = 1; //(0)=off, (1)=1 tab/level, (>1)=that many spaces/level
cson_output_FILE( myValue, stdout, &opt );

The primary reason for setting a maximum output depth is to help avoid endless recursion. Deep levels can be an indication of cycles in an object/array tree, and cycles are strictly forbidden by the API. If cycles appear, outputting may fail and it may result in memory corruption or crashes when the objects are cleaned up.

Extending the Lifetime of In-container Values

The library internally reference counts values as they are added to containers (objects/arrays), so that a given value can be placed in more than one container or in the same container multiple times (e.g., as a memory allocation optimization where a given value is used often). Adding a cson_value instance to a container transfers ownership of that value to the container, meaning that the value's resources will eventually be cleaned up by the container. It may be useful in some cases to ensure that a container-held value has an lifespan independent of that container (or several containers). To do that the client can manually increment the reference count, like this:

cson_value * myValue = ...;
...
cson_value_add_reference( myValue );

The value can then be freed using the normal mechanism:

cson_value_free( myValue );

That will decrease its reference count, cleaning it up if the count drops to 0. If a container (or another call to cson_value_add_reference()) has increased the count, the value won't be cleaned up until the last reference is released.

This can also be used when implementing custom cson_value containers not specified by the JSON API. e.g. when storing cson_value objects in C++ STL container (in particular if a value might be in more than one container), it may be necessary to manually add a reference.

Another way to achieve the longer-lifetime effect is to keep an extra cson_object or cson_array in your application and store all important/re-usable values there (in addition to in any other containers you app uses). In that case, the "extra" container holds its own reference to those values, so those values are guaranteed to live at least as long as the extra container does (it would presumably be cleaned up when the application exits).

Threading

Concurrent read-only access to any given object/array tree is legal, as long as no two threads are using the same traversal data (e.g. the same cson_object_iterator or same copy of an array index variable). All write access to any cson value must be done in a single thread or must be carefully serialized by the client. While write access is going on, any concurrent read access has unspecified results.

The cson_parse() and cson_output() family of functions use no shared state and are thread safe as long as no two threads pass pointers to the same parameter values. e.g. passing the same cson_parse_info object to two concurrent calls to cson_parse() would have undefined results.

The globally-shared state used by the library is limited to:

  • Internal constant objects which are initialized pre-main() and never modified, so these are safe vis-a-vis threads (assuming the threads are not started pre-main(), e.g. as a side-effect of static C++ object initialization). Because they are never modified and C has no destructors, these instance "should" be safe if used post-main(), e.g. via an atexit() handler, assuming my understanding of the exit()/return-from-main() handling is correct.
  • The system's malloc() and friends, so it inherits any threading limitations those may impose.