From 6fb2e33efac609701f02c3acb4bb743e1847b3ef Mon Sep 17 00:00:00 2001 From: scottmk Date: Fri, 25 Sep 2009 14:35:18 +0000 Subject: [PATCH] 1. Add doxygen markup for documentation. 2. In jsonNewObjectType(): explicitly initialize a JSON_BOOL to false, instead of implicitly relying on the expectation that a NULL pointer is represented by all-bits-zero. 3. In jsonObjectExtractIndex(): set the parent pointer to NULL in the extracted jsonObject. M include/opensrf/osrf_json.h M src/libopensrf/osrf_json_object.c git-svn-id: svn://svn.open-ils.org/OpenSRF/trunk@1794 9efc2488-bf62-4759-914b-345cdb29e865 --- include/opensrf/osrf_json.h | 436 +++++++++++++----------- src/libopensrf/osrf_json_object.c | 532 ++++++++++++++++++++++++++++-- 2 files changed, 735 insertions(+), 233 deletions(-) diff --git a/include/opensrf/osrf_json.h b/include/opensrf/osrf_json.h index 4dd8ead..b7fb8fc 100644 --- a/include/opensrf/osrf_json.h +++ b/include/opensrf/osrf_json.h @@ -13,6 +13,38 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +/** + @file osrf_json.h + @brief Header for parsing JSON structures and representing them in memory. + + JSON is a format for representing hierarchical data structures as text. + + A JSON string may be a quoted string, a numeric literal, or any of the keywords true, false + and null. + + A JSON string may also be an array, i.e. a series of values, separated by commas and enclosed + in square brackets. For example: [ "Adams", 42, null, true ] + + A JSON string may also be an object, i.e. a series of name/value pairs, separated by commas + and enclosed by curly braces. Each name/value pair is a quoted string, followed by a colon, + followed by a value. For example: { "Author":"Adams", "answer":42, "question":null, + "profound": true } + + The values in a JSON array or object may themselves be arrays or objects, nested to any + depth, in a hierarchical structure of arbitrary complexity. For more information about + JSON, see http://json.org/. + + Like a JSON string, a jsonObject can take several different forms. It can hold a string, a + number, a boolean, or a null. It can also hold an array, implemented internally by an + osrfList (see osrf_list.h). It can also hold a series of name/value pairs, implemented + internally by an osrfHash (see osrf_hash.h). + + A jsonObject can also tag its contents with a class name, typically referring to a + database table or view. Such an object can be translated into a JSON string where the + class is encoded as the value of a name/value pair, with the original jsonObject encoded + as the value of a second name/value pair. +*/ + #ifndef JSON_H #define JSON_H @@ -25,6 +57,14 @@ extern "C" { #endif /* parser states */ +/** + @name Parser states + @brief Used internally by a JSON parser. + + A jsonParserContext stores these values in order to remember where the parser is in the + parsing. +*/ +/*@{*/ #define JSON_STATE_IN_OBJECT 0x1 #define JSON_STATE_IN_ARRAY 0x2 #define JSON_STATE_IN_STRING 0x4 @@ -40,53 +80,107 @@ extern "C" { #define JSON_STATE_START_COMMEN 0x1000 #define JSON_STATE_IN_COMMENT 0x2000 #define JSON_STATE_END_COMMENT 0x4000 +/*@}*/ +/** + @name Parser state operations + @ Macros to manipulate the parser state in a jsonParserContext. +*/ +/*@{*/ +/** Set a state. */ +#define JSON_STATE_SET(ctx,s) ctx->state |= s; +/** Unset a state. */ +#define JSON_STATE_REMOVE(ctx,s) ctx->state &= ~s; +/** Check if a state is set. */ +#define JSON_STATE_CHECK(ctx,s) (ctx->state & s) ? 1 : 0 +/** Pop a state off the stack. */ +#define JSON_STATE_POP(ctx) osrfListPop( ctx->stateStack ); +/** Push a state on the stack. */ +#define JSON_STATE_PUSH(ctx, state) osrfListPush( ctx->stateStack,(void*) state ); +/** Check which container type we're currently in. */ +#define JSON_STATE_PEEK(ctx) osrfListGetIndex(ctx->stateStack, ctx->stateStack->size -1) +/** Compare stack values. */ +#define JSON_STATE_CHECK_STACK(ctx, s) (JSON_STATE_PEEK(ctx) == (void*) s ) ? 1 : 0 +/** Check if a parser state is set. */ +#define JSON_PARSE_FLAG_CHECK(ctx, f) (ctx->flags & f) ? 1 : 0 +/*@}*/ -/* object and array (container) states are pushed onto a stack so we - * can keep track of the object nest. All other states are - * simply stored in the state field of the parser */ -#define JSON_STATE_SET(ctx,s) ctx->state |= s; /* set a state */ -#define JSON_STATE_REMOVE(ctx,s) ctx->state &= ~s; /* unset a state */ -#define JSON_STATE_CHECK(ctx,s) (ctx->state & s) ? 1 : 0 /* check if a state is set */ -#define JSON_STATE_POP(ctx) osrfListPop( ctx->stateStack ); /* remove a state from the stack */ -#define JSON_STATE_PUSH(ctx, state) osrfListPush( ctx->stateStack,(void*) state );/* push a state on the stack */ -#define JSON_STATE_PEEK(ctx) osrfListGetIndex(ctx->stateStack, ctx->stateStack->size -1) /* check which container type we're currently in */ -#define JSON_STATE_CHECK_STACK(ctx, s) (JSON_STATE_PEEK(ctx) == (void*) s ) ? 1 : 0 /* compare stack values */ +/** + @name JSON types + @brief Macros defining types of jsonObject. -/* JSON types */ + A jsonObject includes a @em type member with one of these values, identifying the type of + jsonObject. Client code should treat the @em type member as read-only. +*/ +/*@{*/ #define JSON_HASH 0 #define JSON_ARRAY 1 #define JSON_STRING 2 #define JSON_NUMBER 3 -#define JSON_NULL 4 +#define JSON_NULL 4 #define JSON_BOOL 5 +/*@}*/ +/** + This macro is used only by a JSON parser. It probably has no business being published + in a header. +*/ #define JSON_PARSE_LAST_CHUNK 0x1 /* this is the last part of the string we're parsing */ -#define JSON_PARSE_FLAG_CHECK(ctx, f) (ctx->flags & f) ? 1 : 0 /* check if a parser state is set */ +/** + @name JSON extensions + + These two macros define tags used for encoding class information. @em JSON_CLASS_KEY + labels a class name, and @em JSON_DATA_KEY labels an associated value. + Because each of these macros is subject to an ifndef clause, client code may override + the default choice of labels by defining alternatives before including this header. + At this writing, this potential for customization is unused. +*/ +/*@{*/ #ifndef JSON_CLASS_KEY #define JSON_CLASS_KEY "__c" #endif #ifndef JSON_DATA_KEY #define JSON_DATA_KEY "__p" #endif +/*@}*/ +/** + @brief Stores the current state of a JSON parser. + + One form of JSON parser operates as a finite state machine. It stores the various + JSON_STATE_* values in order to keep track of what it's doing. It also maintains a + stack of previous states in order to keep track of nesting. + The internals of this struct are published in the header in order to provide the client + with a window into the operations of the parser. By installing its own callback functions, + and possibly by tinkering with the insides of the jsonParserContext, the client code can + customize the behavior of the parser. + + In practice only the default callbacks are ever installed, at this writing. The potential + for customized parsing is unused. +*/ struct jsonParserContextStruct { - int state; /* what are we currently parsing */ - const char* chunk; /* the chunk we're currently parsing */ - int index; /* where we are in parsing the current chunk */ - int chunksize; /* the size of the current chunk */ - int flags; /* parser flags */ - osrfList* stateStack; /* represents the nest of object/array states */ - growing_buffer* buffer; /* used to hold JSON strings, number, true, false, and null sequences */ - growing_buffer* utfbuf; /* holds the current unicode characters */ - void* userData; /* opaque user pointer. we ignore this */ - const struct jsonParserHandlerStruct* handler; /* the event handler struct */ + int state; /**< What are we currently parsing. */ + const char* chunk; /**< The chunk we're currently parsing. */ + int index; /**< Where we are in parsing the current chunk. */ + int chunksize; /**< Size of the current chunk. */ + int flags; /**< Parser flags. */ + osrfList* stateStack; /**< The nest of object/array states. */ + growing_buffer* buffer; /**< Buffer for building strings, numbers, and keywords. */ + growing_buffer* utfbuf; /**< Holds the current unicode characters. */ + void* userData; /**< Opaque user pointer. We ignore this. */ + const struct jsonParserHandlerStruct* handler; /**< The event handler struct. */ }; typedef struct jsonParserContextStruct jsonParserContext; +/** + @brief A collection of function pointers for customizing parser behavior. + + The client code can install pointers to its own functions in this struct, in order to + customize the behavior of the parser at various points in the parsing. +*/ struct jsonParserHandlerStruct { void (*handleStartObject) (void* userData); void (*handleObjectKey) (void* userData, char* key); @@ -94,234 +188,194 @@ struct jsonParserHandlerStruct { void (*handleStartArray) (void* userData); void (*handleEndArray) (void* userData); void (*handleNull) (void* userData); - void (*handleString) (void* userData, char* string); + void (*handleString) (void* userData, char* string); void (*handleBool) (void* userData, int boolval); void (*handleNumber) (void* userData, const char* numstr); void (*handleError) (void* userData, char* err, ...); }; typedef struct jsonParserHandlerStruct jsonParserHandler; +/** + @brief Representation of a JSON string in memory + + Different types of jsonObject use different members of the @em value union. + + Numbers are stored as character strings, using the same buffer that we use for + strings. As a result, a JSON_NUMBER can store numbers that cannot be represented + by native C types. + + (We used to store numbers as doubles. We still have the @em n member lying around as + a relic of those times, but we don't use it. We can't get rid of it yet, either. Long + story.) +*/ struct _jsonObjectStruct { - unsigned long size; /* number of sub-items */ - char* classname; /* optional class hint (not part of the JSON spec) */ - int type; /* JSON type */ - struct _jsonObjectStruct* parent; /* who we're attached to */ - union __jsonValue { /* cargo */ - osrfHash* h; /* object container */ - osrfList* l; /* array container */ - char* s; /* string */ - int b; /* bool */ -// double n; /* number */ - double n; /* number */ + unsigned long size; /**< Number of sub-items. */ + char* classname; /**< Optional class hint (not part of the JSON spec). */ + int type; /**< JSON type. */ + struct _jsonObjectStruct* parent; /**< Whom we're attached to. */ + /** Union used for various types of cargo. */ + union _jsonValue { + osrfHash* h; /**< Object container. */ + osrfList* l; /**< Array container. */ + char* s; /**< String or number. */ + int b; /**< Bool. */ + double n; /**< Number (no longer used). */ } value; }; typedef struct _jsonObjectStruct jsonObject; +/** + @brief Iterator for traversing a jsonObject. + + A jsonIterator traverses a jsonIterator only at a single level. It does @em not descend + into lower levels to traverse them recursively. +*/ struct _jsonIteratorStruct { - jsonObject* obj; /* the object we're traversing */ - osrfHashIterator* hashItr; /* the iterator for this hash */ - const char* key; /* if this object is a hash, the current key */ - unsigned long index; /* if this object is an array, the index */ + jsonObject* obj; /**< The object we're traversing. */ + osrfHashIterator* hashItr; /**< The iterator for this hash. */ + const char* key; /**< If this object is a hash, the current key. */ + unsigned long index; /**< If this object is an array, the index. */ }; typedef struct _jsonIteratorStruct jsonIterator; /** - * Allocates a new parser context object - * @param handler The event handler struct - * @param userData Opaque user pointer which is available in callbacks - * and ignored by the parser - * @return An allocated parser context, NULL on error - */ + @brief Allocate a new parser context object. + @param handler Pointer to a collection of function pointers for callback functions. + @param userData Opaque user pointer which is available to callbacks; ignored by the parser. + @return An allocated parser context, or NULL on error. +*/ jsonParserContext* jsonNewParser( const jsonParserHandler* handler, void* userData); /** - * Deallocates a parser context - * @param ctx The context object - */ + @brief Free a jsonParserContext. + @param ctx Pointer to the jsonParserContext to be freed. +*/ void jsonParserFree( jsonParserContext* ctx ); /** - * Parse a chunk of data. - * @param ctx The parser context - * @param data The data to parse - * @param datalen The size of the chunk to parser - * @param flags Reserved - */ + @brief Parse a chunk of data. + @param ctx Pointer to the parser context. + @param data Pointer the data to parse. + @param datalen The size of the chunk to parse. + @param flags Reserved. +*/ int jsonParseChunk( jsonParserContext* ctx, const char* data, int datalen, int flags ); +/** + @name Parsing functions + + There are two sets of parsing functions, which are mostly plug-compatible with each other. + The older series: + - jsonParseString() + - jsonParseStringRaw() + - jsonParseStringFmt() + + ...and a newer series: + + - jsonParseString(); + - jsonParseStringRaw(); + - jsonParseStringFmt(); + + The first series is based on a finite state machine. Its innards are accessible, in + theory, through the jsonParserContext structure and through callback functions. In + practice this flexibility is unused at this writing. + + The second series is based on recursive descent. It doesn't use the jsonParserContext + structure, nor does it accept callback functions. However it is faster than the first + parser. In addition its syntax checking is much stricter -- it catches many kinds of + syntax errors that slip through the first parser. +*/ +/*@{*/ /** - * Parses a JSON string; - * @param str The string to parser - * @return The resulting JSON object or NULL on error - */ + @brief Parse a JSON string; + @param str Pointer to the JSON string to parse. + @return The resulting JSON object, or NULL on error. +*/ jsonObject* jsonParseString( const char* str ); jsonObject* jsonParseStringRaw( const char* str ); - jsonObject* jsonParseStringFmt( const char* str, ... ); /** - * Similar to jsonParseString(), jsonParseStringRaw() and - * jsonParseStringFmt(), but with stricter and more robust - * syntax validation + @brief Parse a JSON string; + @param s Pointer to the JSON string to parse. + @return The resulting JSON object, or NULL on error. */ jsonObject* jsonParse( const char* s ); jsonObject* jsonParseRaw( const char* s ); jsonObject* jsonParseFmt( const char* str, ... ); - +/*@}*/ /** - * Parses a JSON string; - * @param str The string to parser - * @return The resulting JSON object or NULL on error + @brief Parses a JSON string, using a customized error handler. + @param errorHandler A function pointer to an error-handling function. + @param str The string to parse. + @return The resulting JSON object, or NULL on error. */ jsonObject* jsonParseStringHandleError( void (*errorHandler) (const char*), char* str, ... ); - - -/** - * Creates a new json object - * @param data The string data this object will hold if - * this object happens to be a JSON_STRING, NULL otherwise - * @return The allocated json object. Must be freed with - * jsonObjectFree() - */ jsonObject* jsonNewObject(const char* data); + jsonObject* jsonNewObjectFmt(const char* data, ...); -/** - * Creates a new object of the given type - */ jsonObject* jsonNewObjectType(int type); -/** - * Creates a new number object from a double - */ jsonObject* jsonNewNumberObject( double num ); -/** - * Creates a new number object from a numeric string - */ jsonObject* jsonNewNumberStringObject( const char* numstr ); -/** - * Creates a new json bool - */ jsonObject* jsonNewBoolObject(int val); -/** - * Deallocates an object - */ void jsonObjectFree( jsonObject* o ); -/** - * Returns all unused jsonObjects to the heap - */ void jsonObjectFreeUnused( void ); -/** - * Forces the given object to become an array (if it isn't already one) - * and pushes the new object into the array - */ unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo); -/** - * Forces the given object to become a hash (if it isn't already one) - * and assigns the new object to the key of the hash - */ unsigned long jsonObjectSetKey( jsonObject* o, const char* key, jsonObject* newo); - -/** +/* * Turns the object into a JSON string. The string must be freed by the caller */ char* jsonObjectToJSON( const jsonObject* obj ); char* jsonObjectToJSONRaw( const jsonObject* obj ); - -/** - * Retrieves the object at the given key - */ jsonObject* jsonObjectGetKey( jsonObject* obj, const char* key ); -const jsonObject* jsonObjectGetKeyConst( const jsonObject* obj, const char* key ); - - - +const jsonObject* jsonObjectGetKeyConst( const jsonObject* obj, const char* key ); -/** Allocates a new iterator - @param obj The object over which to iterate. -*/ jsonIterator* jsonNewIterator(const jsonObject* obj); +void jsonIteratorFree(jsonIterator* itr); -/** - De-allocates an iterator - @param iter The iterator object to free -*/ -void jsonIteratorFree(jsonIterator* iter); - -/** - Returns the object_node currently pointed to by the iterator - and increments the pointer to the next node - @param iter The iterator in question. - */ jsonObject* jsonIteratorNext(jsonIterator* iter); +int jsonIteratorHasNext(const jsonIterator* itr); -/** - @param iter The iterator. - @return True if there is another node after the current node. - */ -int jsonIteratorHasNext(const jsonIterator* iter); - - -/** - Returns a pointer to the object at the given index. This call is - only valid if the object has a type of JSON_ARRAY. - @param obj The object - @param index The position within the object - @return The object at the given index. -*/ jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index ); - -/* removes (and deallocates) the object at the given index (if one exists) and inserts - * the new one. returns the size on success, -1 on error - * If obj is NULL, inserts a new object into the list with is_null set to true - */ unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObject* newObj); -/* removes and deallocates the object at the given index, replacing - it with a NULL pointer - */ unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index); -/* removes (but does not deallocate) the object at the given index, - * replacing it with a NULL pointer; returns a pointer to the object removed - */ jsonObject* jsonObjectExtractIndex(jsonObject* dest, unsigned long index); -/* removes (and deallocates) the object with key 'key' if it exists */ unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key); -/* returns a pointer to the string data held by this object if this object - is a string. Otherwise returns NULL*/ char* jsonObjectGetString(const jsonObject*); double jsonObjectGetNumber( const jsonObject* obj ); -/* sets the string data */ void jsonObjectSetString(jsonObject* dest, const char* string); -/* sets the number value for the object */ void jsonObjectSetNumber(jsonObject* dest, double num); + int jsonObjectSetNumberString(jsonObject* dest, const char* string); -/* sets the class hint for this object */ void jsonObjectSetClass(jsonObject* dest, const char* classname ); + const char* jsonObjectGetClass(const jsonObject* dest); int jsonBoolIsTrue( const jsonObject* boolObj ); @@ -330,79 +384,68 @@ void jsonSetBool(jsonObject* bl, int val); jsonObject* jsonObjectClone( const jsonObject* o ); - -/* tries to extract the string data from an object. - if object -> NULL (the C NULL) - if array -> NULL - if null -> NULL - if bool -> NULL - if string/number the string version of either of those - The caller is responsible for freeing the returned string - */ char* jsonObjectToSimpleString( const jsonObject* o ); -/** - Allocate a buffer and format a specified numeric value into it, - with up to 30 decimal digits of precision. Caller is responsible - for freeing the buffer. - **/ char* doubleToString( double num ); -/** - Return 1 if the string is numeric, otherwise return 0. - This validation follows the rules defined by the grammar at: - http://www.json.org/ - **/ int jsonIsNumeric( const char* s ); -/** - Allocate and reformat a numeric string into one that is valid - by JSON rules. If the string is not numeric, return NULL. - Caller is responsible for freeing the buffer. - **/ char* jsonScrubNumber( const char* s ); -/* provides an XPATH style search interface (e.g. /some/node/here) and - return the object at that location if one exists. Naturally, - every element in the path must be a proper object ("hash" / {}). - Returns NULL if the specified node is not found - Note also that the object returned is a clone and - must be freed by the caller +/** + @brief Provide an XPATH style search interface to jsonObjects. + @param obj Pointer to the jsonObject to be searched. + @param path Pointer to a printf-style format string specifying the search path. Subsequent + parameters, if any, are formatted and inserted into the formatted string. + @return A copy of the object at the specified location, if it exists, or NULL if it doesn't. + + Example search path: /some/node/here. + + Every element in the path must be a proper object, a JSON_HASH. + + The calling code is responsible for freeing the jsonObject to which the returned pointer + points. */ jsonObject* jsonObjectFindPath( const jsonObject* obj, const char* path, ... ); -/* formats a JSON string from printing. User must free returned string */ +/** + @brief Prettify a JSON string for printing, by adding newlines and other white space. + @param jsonString Pointer to the original JSON string. + @return Pointer to a prettified JSON string. + + The calling code is responsible for freeing the returned string. +*/ char* jsonFormatString( const char* jsonString ); /* sets the error handler for all parsers */ void jsonSetGlobalErrorHandler(void (*errorHandler) (const char*)); /* ------------------------------------------------------------------------- */ -/** +/* * The following methods provide a facility for serializing and * deserializing "classed" JSON objects. To give a JSON object a - * class, simply call jsonObjectSetClass(). + * class, simply call jsonObjectSetClass(). * Then, calling jsonObjectEncodeClass() will convert the JSON - * object (and any sub-objects) to a JSON object with class + * object (and any sub-objects) to a JSON object with class * wrapper objects like so: - * { _c : "classname", _d : } - * In this example _c is the class key and _d is the data (object) + * { "__c" : "classname", "__p" : [json_thing] } + * In this example __c is the class key and __p is the data (object) * key. The keys are defined by the constants - * OSRF_JSON_CLASS_KEY and OSRF_JSON_DATA_KEY + * JSON_CLASS_KEY and JSON_DATA_KEY * To revive a serialized object, simply call * jsonObjectDecodeClass() */ -/** Converts a class-wrapped object into an object with the +/* Converts a class-wrapped object into an object with the * classname set * Caller must free the returned object */ jsonObject* jsonObjectDecodeClass( const jsonObject* obj ); -/** Converts an object with a classname into a +/* Converts an object with a classname into a * class-wrapped (serialized) object * Caller must free the returned object */ @@ -411,11 +454,10 @@ jsonObject* jsonObjectEncodeClass( const jsonObject* obj ); /* ------------------------------------------------------------------------- */ -/** +/* * Generates an XML representation of a JSON object */ char* jsonObjectToXML(const jsonObject*); - /* * Builds a JSON object from the provided XML */ diff --git a/src/libopensrf/osrf_json_object.c b/src/libopensrf/osrf_json_object.c index 0cab256..430cb32 100644 --- a/src/libopensrf/osrf_json_object.c +++ b/src/libopensrf/osrf_json_object.c @@ -13,6 +13,17 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ +/** + @file osrf_json_object.c + @brief Implementation of the basic operations involving jsonObjects. + + As a performance tweak: we maintain a free list of jsonObjects that have already + been allocated but are not currently in use. When we need to create a jsonObject, + we can take one from the free list, if one is available, instead of calling + malloc(). Likewise when we free a jsonObject, we can stick it on the free list + for potential reuse instead of calling free(). +*/ + #include #include #include @@ -24,6 +35,19 @@ GNU General Public License for more details. /* cleans up an object if it is morphing another object, also * verifies that the appropriate storage container exists where appropriate */ +/** + @brief Coerce a jsonObj into a specified type, if it isn't already of that type. + @param _obj_ Pointer to the jsonObject to be coerced. + @param newtype The desired type. + + If the old type and the new type don't match, discard and free the old contents. + + If the old type is JSON_STRING or JSON_NUMBER, free the internal string buffer even + if the type is not changing. + + If the new type is JSON_ARRAY or JSON_HASH, make sure there is an osrfList or osrfHash + in the jsonObject, respectively. +*/ #define JSON_INIT_CLEAR(_obj_, newtype) \ if( _obj_->type == JSON_HASH && newtype != JSON_HASH ) { \ osrfHashFree(_obj_->value.h); \ @@ -46,11 +70,19 @@ GNU General Public License for more details. _obj_->value.l->freeItem = _jsonFreeListItem;\ } +/** Count of the times we put a freed jsonObject on the free list instead of calling free() */ static int unusedObjCapture = 0; +/** Count of the times we reused a jsonObject from the free list instead of calling malloc() */ static int unusedObjRelease = 0; +/** Count of the times we allocated a jsonObject with malloc() */ static int mallocObjCreate = 0; +/** Number of unused jsonObjects currently on the free list */ static int currentListLen = 0; +/** + Union overlaying a jsonObject with a pointer. When the jsonObject is not in use as a + jsonObject, we use the overlaid pointer to maintain a linked list of unused jsonObjects. +*/ union unusedObjUnion{ union unusedObjUnion* next; @@ -58,19 +90,20 @@ union unusedObjUnion{ }; typedef union unusedObjUnion unusedObj; -// We maintain a free list of jsonObjects that are available -// for use, in order to reduce the churning through -// malloc() and free(). - +/** Pointer to the head of the free list */ static unusedObj* freeObjList = NULL; static void add_json_to_buffer( const jsonObject* obj, growing_buffer * buf, int do_classname, int second_pass ); /** - * Return all unused jsonObjects to the heap - * @return Nothing - */ + @brief Return all jsonObjects in the free list to the heap. + + Reclaims memory occupied by unused jsonObjects in the free list. It is never really + necessary to call this function, assuming that we don't run out of memory. However + it might be worth calling if we have built and destroyed a lot of jsonObjects that + we don't expect to need again, in order to reduce our memory footprint. +*/ void jsonObjectFreeUnused( void ) { unusedObj* temp; @@ -81,10 +114,22 @@ void jsonObjectFreeUnused( void ) { } } +/** + @brief Create a new jsonObject, optionally containing a string. + @param data Pointer to a string to be stored in the jsonObject; may be NULL. + @return Pointer to a newly allocate jsonObject. + + If @a data is NULL, create a jsonObject of type JSON_NULL. Otherwise create + a jsonObject of type JSON_STRING, containing the specified string. + + The calling code is responsible for freeing the jsonObject by calling jsonObjectFree(). +*/ jsonObject* jsonNewObject(const char* data) { jsonObject* o; + // Allocate a jsonObject; from the free list if possible, + // or from the heap if necessary. if( freeObjList ) { o = (jsonObject*) freeObjList; freeObjList = freeObjList->next; @@ -110,6 +155,17 @@ jsonObject* jsonNewObject(const char* data) { return o; } +/** + @brief Create a jsonObject, optionally containing a formatted string. + @param data Pointer to a printf-style format string; may be NULL. Subsequent parameters, + if any, will be formatted and inserted into the resulting string. + @return Pointer to a newly created jsonObject. + + If @a data is NULL, create a jsonObject of type JSON_NULL, and ignore any extra parameters. + Otherwise create a jsonObject of type JSON_STRING, containing the formatted string. + + The calling code is responsible for freeing the jsonObject by calling jsonObjectFree(). + */ jsonObject* jsonNewObjectFmt(const char* data, ...) { jsonObject* o; @@ -141,6 +197,16 @@ jsonObject* jsonNewObjectFmt(const char* data, ...) { return o; } +/** + @brief Create a new jsonObject of type JSON_NUMBER. + @param num The number to store in the jsonObject. + @return Pointer to the newly created jsonObject. + + The number is stored internally as a character string, as formatted by + doubleToString(). + + The calling code is responsible for freeing the jsonObject by calling jsonObjectFree(). +*/ jsonObject* jsonNewNumberObject( double num ) { jsonObject* o = jsonNewObject(NULL); o->type = JSON_NUMBER; @@ -149,8 +215,15 @@ jsonObject* jsonNewNumberObject( double num ) { } /** - * Creates a new number object from a numeric string - */ + @brief Create a new jsonObject of type JSON_NUMBER from a numeric string. + @param numstr Pointer to a numeric character string. + @return Pointer to a newly created jsonObject containing the numeric value, or NULL + if the string is not numeric. + + The jsonIsNumeric() function determines whether the input string is numeric. + + The calling code is responsible for freeing the jsonObject by calling jsonObjectFree(). +*/ jsonObject* jsonNewNumberStringObject( const char* numstr ) { if( !numstr ) numstr = "0"; @@ -163,6 +236,17 @@ jsonObject* jsonNewNumberStringObject( const char* numstr ) { return o; } +/** + @brief Create a new jsonObject of type JSON_BOOL, with a specified boolean value. + @param val An int used as a boolean value. + @return Pointer to the new jsonObject. + + Zero represents false, and non-zero represents true, according to the usual convention. + In practice the value of @a val is stored unchanged, but the calling code should not try to + take advantage of that fact, because future versions may behave differently. + + The calling code is responsible for freeing the jsonObject by calling jsonObjectFree(). +*/ jsonObject* jsonNewBoolObject(int val) { jsonObject* o = jsonNewObject(NULL); o->type = JSON_BOOL; @@ -170,12 +254,35 @@ jsonObject* jsonNewBoolObject(int val) { return o; } +/** + @brief Create a new jsonObject of a specified type, with a default value. + @param type One of the 6 JSON types, as specified by the JSON_* macros. + @return Pointer to the new jsonObject. + + An invalid type parameter will go unnoticed, and may lead to unpleasantness. + + The default value is equivalent to an empty string (for a JSON_STRING), zero (for a + JSON_NUMBER), an empty hash (for a JSON_HASH), an empty array (for a JSON_ARRAY), or + false (for a JSON_BOOL). A JSON_NULL, of course, has no value, but can still be + useful. + + The calling code is responsible for freeing the jsonObject by calling jsonObjectFree(). +*/ jsonObject* jsonNewObjectType(int type) { jsonObject* o = jsonNewObject(NULL); o->type = type; + if( JSON_BOOL == type ) + o->value.b = 0; return o; } +/** + @brief Free a jsonObject and everything in it. + @param o Pointer to the jsonObject to be freed. + + Any jsonObjects stored inside the jsonObject (in hashes or arrays) will be freed as + well, and so one, recursively. +*/ void jsonObjectFree( jsonObject* o ) { if(!o || o->parent) return; @@ -204,12 +311,26 @@ void jsonObjectFree( jsonObject* o ) { mallocObjCreate, unusedObjCapture, unusedObjRelease, currentListLen ); } +/** + @brief Free a jsonObject through a void pointer. + @param key Not used. + @param item Pointer to the jsonObject to be freed, cast to a void pointer. + + This function is a callback for freeing jsonObjects stored in an osrfHash. +*/ static void _jsonFreeHashItem(char* key, void* item){ if(!item) return; jsonObject* o = (jsonObject*) item; o->parent = NULL; /* detach the item */ jsonObjectFree(o); } + +/** + @brief Free a jsonObject through a void pointer. + @param item Pointer to the jsonObject to be freed, cast to a void pointer. + + This function is a callback for freeing jsonObjects stored in an osrfList. + */ static void _jsonFreeListItem(void* item){ if(!item) return; jsonObject* o = (jsonObject*) item; @@ -217,12 +338,39 @@ static void _jsonFreeListItem(void* item){ jsonObjectFree(o); } +/** + @brief Assign a boolean value to a jsonObject of type JSON_BOOL. + @param bl Pointer to the jsonObject. + @param val The boolean value to be applied, encoded as an int. + + If the jsonObject is not already of type JSON_BOOL, it is converted to one, and + any previous contents are freed. + + Zero represents false, and non-zero represents true, according to the usual convention. + In practice the value of @a val is stored unchanged, but the calling code should not try to + take advantage of that fact, because future versions may behave differently. +*/ void jsonSetBool(jsonObject* bl, int val) { if(!bl) return; JSON_INIT_CLEAR(bl, JSON_BOOL); bl->value.b = val; } +/** + @brief Insert one jsonObject into a jsonObect of type JSON_ARRAY, at the end of the array. + @param o Pointer to the outer jsonObject that will receive additional payload. + @param newo Pointer to the inner jsonObject, or NULL. + @return The number of jsonObjects directly subordinate to the outer jsonObject, + or -1 for error. + + If the pointer to the outer jsonObject is NULL, jsonObjectPush returns -1. + + If the outer jsonObject is not already of type JSON_ARRAY, it is converted to one, and + any previous contents are freed. + + If the pointer to the inner jsonObject is NULL, jsonObjectPush creates a new jsonObject + of type JSON_NULL, and appends it to the array. +*/ unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo) { if(!o) return -1; if(!newo) newo = jsonNewObject(NULL); @@ -233,9 +381,31 @@ unsigned long jsonObjectPush(jsonObject* o, jsonObject* newo) { return o->size; } +/** + @brief Insert a jsonObject at a specified position in a jsonObject of type JSON_ARRAY. + @param dest Pointer to the outer jsonObject that will receive the new payload. + @param index A zero-based subscript specifying the position of the new jsonObject. + @param newObj Pointer to the new jsonObject to be inserted, or NULL. + @return The size of the internal osrfList where the array is stored, or -1 on error. + + If @a dest is NULL, jsonObjectSetIndex returns -1. + + If the outer jsonObject is not already of type JSON_ARRAY, it is converted to one, and + any previous contents are freed. + + If @a newObj is NULL, jsonObject creates a new jsonObject of type JSON_NULL and + inserts it into the array. + + If there is already a jsonObject at the specified location, it is freed and replaced. + + Depending on the placement of the inner jsonObject, it may leave unoccupied holes in the + array that are included in the reported size. As a result of this and other peculiarities + of the underlying osrfList within the jsonObject, the reported size may not reflect the + number of jsonObjects in the array. See osrf_list.c for further details. +*/ unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObject* newObj) { - if(!dest) return -1; - if(!newObj) newObj = jsonNewObject(NULL); + if(!dest) return -1; + if(!newObj) newObj = jsonNewObject(NULL); JSON_INIT_CLEAR(dest, JSON_ARRAY); newObj->parent = dest; osrfListSet( dest->value.l, newObj, index ); @@ -243,6 +413,26 @@ unsigned long jsonObjectSetIndex(jsonObject* dest, unsigned long index, jsonObje return dest->value.l->size; } +/** + @brief Insert a jsonObject into a jsonObject of type JSON_HASH, for a specified key. + @param o Pointer to the outer jsonObject that will receive the new payload. + @param key Pointer to a string that will serve as the key. + @param newo Pointer to the new jsonObject that will be inserted, or NULL. + @return The number of items stored in the first level of the hash. + + If the @a o parameter is NULL, jsonObjectSetKey returns -1. + + If the outer jsonObject is not already of type JSON_HASH, it will be converted to one, + and any previous contents will be freed. + + If @a key is NULL, jsonObjectSetKey returns the size of the hash without doing anything + (apart from possibly converting the outer jsonObject as described above). + + If @a newo is NULL, jsonObjectSetKey creates a new jsonObject of type JSON_NULL and inserts + it with the specified key. + + If a previous jsonObject is already stored with the same key, it is freed and replaced. +*/ unsigned long jsonObjectSetKey( jsonObject* o, const char* key, jsonObject* newo) { if(!o) return -1; if(!newo) newo = jsonNewObject(NULL); @@ -253,31 +443,52 @@ unsigned long jsonObjectSetKey( jsonObject* o, const char* key, jsonObject* newo return o->size; } +/** + @brief From a jsonObject of type JSON_HASH, find the inner jsonObject for a specified key. + @param obj Pointer to the outer jsonObject. + @param key The key for the associated item to be found. + @return A pointer to the inner jsonObject, if found, or NULL if not. + + Returns NULL if either parameter is NULL, or if @a obj points to a jsonObject not of type + JSON_HASH, or if the specified key is not found in the outer jsonObject. + + The returned pointer (if not NULL) points to the interior of the outer jsonObject. The + calling code should @em not try to free it, but it may change its contents. +*/ jsonObject* jsonObjectGetKey( jsonObject* obj, const char* key ) { if(!(obj && obj->type == JSON_HASH && obj->value.h && key)) return NULL; return osrfHashGet( obj->value.h, key); } +/** + @brief From a jsonObject of type JSON_HASH, find the inner jsonObject for a specified key. + @param obj Pointer to the outer jsonObject. + @param key The key for the associated item to be found. + @return A pointer to the inner jsonObject, if found, or NULL if not. + + This function is identical to jsonObjectGetKey(), except that the outer jsonObject may be + const, and the pointer returned points to const. As a result, the calling code is put on + notice that it should not try to modify the contents of the inner jsonObject. + */ const jsonObject* jsonObjectGetKeyConst( const jsonObject* obj, const char* key ) { if(!(obj && obj->type == JSON_HASH && obj->value.h && key)) return NULL; return osrfHashGet( obj->value.h, key); } /** - * Recursively traverse a jsonObject, formatting it into a JSON string. - * - * The last two parameters are booleans. - * - * If do_classname is true, examine each node for a classname, and if you - * find one, pretend that the node is under an extra layer of JSON_HASH, with - * JSON_CLASS_KEY and JSON_DATA_KEY as keys. - * - * second_pass should always be false except for some recursive calls. It - * is used when expanding classnames, to distinguish between the first and - * second passes through a given node. - * - * @return Nothing - */ + @brief Recursively traverse a jsonObject, translating it into a JSON string. + @param obj Pointer to the jsonObject to be translated. + @param buf Pointer to a growing_buffer that will receive the JSON string. + @param do_classname Boolean; if true, expand class names. + @param second_pass Boolean; should always be false except for some recursive calls. + + If @a do_classname is true, expand any class names, as described in the discussion of + jsonObjectToJSON(). + + @a second_pass should always be false except for some recursive calls. It is used + when expanding classnames, to distinguish between the first and second passes + through a given node. +*/ static void add_json_to_buffer( const jsonObject* obj, growing_buffer * buf, int do_classname, int second_pass ) { @@ -366,6 +577,13 @@ static void add_json_to_buffer( const jsonObject* obj, } } +/** + @brief Translate a jsonObject into a JSON string, without expanding class names. + @param obj Pointer to the jsonObject to be translated. + @return A pointer to a newly allocated string containing the JSON. + + The calling code is responsible for freeing the resulting string. +*/ char* jsonObjectToJSONRaw( const jsonObject* obj ) { if(!obj) return NULL; growing_buffer* buf = buffer_init(32); @@ -373,6 +591,17 @@ char* jsonObjectToJSONRaw( const jsonObject* obj ) { return buffer_release( buf ); } +/** + @brief Translate a jsonObject into a JSON string, with expansion of class names. + @param obj Pointer to the jsonObject to be translated. + @return A pointer to a newly allocated string containing the JSON. + + At every level, any jsonObject containing a class name will be translated as if it were + under an extra layer of JSON_HASH, with JSON_CLASS_KEY as the key for the class name and + JSON_DATA_KEY as the key for the jsonObject. + + The calling code is responsible for freeing the resulting string. + */ char* jsonObjectToJSON( const jsonObject* obj ) { if(!obj) return NULL; growing_buffer* buf = buffer_init(32); @@ -380,6 +609,18 @@ char* jsonObjectToJSON( const jsonObject* obj ) { return buffer_release( buf ); } +/** + @brief Create a new jsonIterator for traversing a specified jsonObject. + @param obj Pointer to the jsonObject to be traversed. + @return A pointer to the newly allocated jsonIterator, or NULL upon error. + + jsonNewIterator returns NULL if @a obj is NULL. + + The new jsonIterator does not point to any particular position within the jsonObject to + be traversed. The next call to jsonIteratorNext() will position it at the beginning. + + The calling code is responsible for freeing the jsonIterator by calling jsonIteratorFree(). +*/ jsonIterator* jsonNewIterator(const jsonObject* obj) { if(!obj) return NULL; jsonIterator* itr; @@ -397,12 +638,42 @@ jsonIterator* jsonNewIterator(const jsonObject* obj) { return itr; } +/** + @brief Free a jsonIterator and everything in it. + @param itr Pointer to the jsonIterator to be freed. +*/ void jsonIteratorFree(jsonIterator* itr) { if(!itr) return; osrfHashIteratorFree(itr->hashItr); free(itr); } +/** + @brief Advance a jsonIterator to the next position within a jsonObject. + @param itr Pointer to the jsonIterator to be advanced. + @return A Pointer to the next jsonObject within the jsonObject being traversed; or NULL. + + If the jsonObject being traversed is of type JSON_HASH, jsonIteratorNext returns a pointer + to the next jsonObject within the internal osrfHash. The associated key string is available + via the pointer member itr->key. + + If the jsonObject being traversed is of type JSON_ARRAY, jsonIteratorNext returns a pointer + to the next jsonObject within the internal osrfList. + + In either case, the jsonIterator remains at the same level within the jsonObject that it is + traversing. It does @em not descend to traverse deeper levels recursively. + + If there is no next jsonObject within the jsonObject being traversed, jsonIteratorNext + returns NULL. It also returns NULL if @a itr is NULL, or if the jsonIterator is + detectably corrupted, or if the jsonObject to be traversed is of a type other than + JSON_HASH or JSON_ARRAY. + + Once jsonIteratorNext has returned NULL, subsequent calls using the same iterator will + continue to return NULL. There is no available function to start over at the beginning. + + The pointer returned, if not NULL, points to an internal element of the jsonObject being + traversed. The calling code should @em not try to free it, but it may modify its contents. +*/ jsonObject* jsonIteratorNext(jsonIterator* itr) { if(!(itr && itr->obj)) return NULL; if( itr->obj->type == JSON_HASH ) { @@ -420,6 +691,14 @@ jsonObject* jsonIteratorNext(jsonIterator* itr) { } } +/** + @brief Determine whether a jsonIterator is positioned at the last element of a jsonObject. + @param itr Pointer to the jsonIterator whose position is to be tested. + @return An int, as boolean: 0 if the iterator is positioned at the end, or 1 if it isn't. + + If the jsonIterator is positioned where a call to jsonIteratorNext() would return NULL, + then jsonIteratorHasNext returns 0. Otherwise it returns 1. +*/ int jsonIteratorHasNext(const jsonIterator* itr) { if(!(itr && itr->obj)) return 0; if( itr->obj->type == JSON_HASH ) @@ -427,14 +706,34 @@ int jsonIteratorHasNext(const jsonIterator* itr) { return (itr->index < itr->obj->size) ? 1 : 0; } +/** + @brief Fetch a pointer to a specified element within a jsonObject of type JSON_ARRAY. + @param obj Pointer to the outer jsonObject. + @param index A zero-based index identifying the element to be fetched. + @return A pointer to the element at the specified location, if any, or NULL. + + The return value is NULL if @a obj is null, or if the outer jsonObject is not of type + JSON_ARRAY, or if there is no element at the specified location. + + If not NULL, the pointer returned points to an element within the outer jsonObject. The + calling code should @em not try to free it, but it may modify its contents. +*/ jsonObject* jsonObjectGetIndex( const jsonObject* obj, unsigned long index ) { if(!obj) return NULL; return (obj->type == JSON_ARRAY) ? (OSRF_LIST_GET_INDEX(obj->value.l, index)) : NULL; } - - +/** + @brief Remove a specified element from a jsonObject of type JSON_ARRAY. + @param dest Pointer to the jsonObject from which the element is to be removed. + @param index A zero-based index identifying the element to be removed. + @return The number of elements remaining at the top level, or -1 upon error. + + The return value is -1 if @a dest is NULL, or if it points to a jsonObject not of type + JSON_ARRAY. Otherwise it reflects the number of elements remaining in the top level of + the outer jsonObject, not counting any at lower levels. +*/ unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index) { if( dest && dest->type == JSON_ARRAY ) { osrfListRemove(dest->value.l, index); @@ -443,15 +742,41 @@ unsigned long jsonObjectRemoveIndex(jsonObject* dest, unsigned long index) { return -1; } +/** + @brief Extract a specified element from a jsonObject of type JSON_ARRAY. + @param dest Pointer to the jsonObject from which the element is to be extracted. + @param index A zero-based index identifying the element to be extracted. + @return A pointer to the extracted element, if successful; otherwise NULL. + + THe return value is NULL if @a dest is NULL, or if it points to a jsonObject not of type + JSON_ARRAY, or if there is no element at the specified location. + + Otherwise, the calling code assumes ownership of the jsonObject to which the return value + points, and is responsible for freeing it by calling jsonObjectFree(). The original outer + jsonObject remains unchanged except for the removal of the specified element. + This function is sijmilar to jsonObjectRemoveIndex(), except that it returns a pointer to + the removed sub-object instead of destroying it. +*/ jsonObject* jsonObjectExtractIndex(jsonObject* dest, unsigned long index) { if( dest && dest->type == JSON_ARRAY ) { - return osrfListExtract(dest->value.l, index); + jsonObject* obj = osrfListExtract(dest->value.l, index); + if( obj ) + obj->parent = NULL; + return obj; } else return NULL; } +/** + @brief Remove an element, specified by key, from a jsonObject of type JSON_HASH. + @param dest Pointer to the outer jsonObject from which an element is to be removed. + @param key The key for the associated element to be removed. + @return 1 if successful, or -1 if not. + The operation is considered successful if @a dest and @a key are both non-NULL, and + @a dest points to a jsonObject of type JSON_HASH, even if the specified key is not found. +*/ unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key) { if( dest && key && dest->type == JSON_HASH ) { osrfHashRemove(dest->value.h, key); @@ -461,11 +786,17 @@ unsigned long jsonObjectRemoveKey( jsonObject* dest, const char* key) { } /** - Allocate a buffer and format a specified numeric value into it. - Caller is responsible for freeing the buffer. -**/ + @brief Format a double into a character string. + @param num The double to be formatted. + @return A newly allocated character string containing the formatted number. + + The number is formatted according to the printf-style format specification "%.30g". and + will therefore contain no more than 30 significant digits. + + The calling code is responsible for freeing the resulting string. +*/ char* doubleToString( double num ) { - + char buf[ 64 ]; size_t len = snprintf(buf, sizeof( buf ), "%.30g", num) + 1; if( len < sizeof( buf ) ) @@ -480,6 +811,19 @@ char* doubleToString( double num ) { } } +/** + @brief Fetch a pointer to the string stored in a jsonObject, if any. + @param obj Pointer to the jsonObject. + @return Pointer to the string stored internally, or NULL. + + If @a obj points to a jsonObject of type JSON_STRING or JSON_NUMBER, the returned value + points to the string stored internally (a numeric string in the case of a JSON_NUMBER). + Otherwise the returned value is NULL. + + The returned pointer should be treated as a pointer to const. In particular it should + @em not be freed. In a future release, the returned pointer may indeed be a pointer + to const. +*/ char* jsonObjectGetString(const jsonObject* obj) { if(obj) { @@ -494,11 +838,29 @@ char* jsonObjectGetString(const jsonObject* obj) { return NULL; } +/** + @brief Translate a jsonObject to a double. + @param @obj Pointer to the jsonObject. + @return The numeric value stored in the jsonObject. + + If @a obj is NULL, or if it points to a jsonObject not of type JSON_NUMBER, the value + returned is zero. +*/ double jsonObjectGetNumber( const jsonObject* obj ) { return (obj && obj->type == JSON_NUMBER && obj->value.s) ? strtod( obj->value.s, NULL ) : 0; } +/** + @brief Store a copy of a specified character string in a jsonObject of type JSON_STRING. + @param dest Pointer to the jsonObject in which the string will be stored. + @param string Pointer to the string to be stored. + + Both @a dest and @a string must be non-NULL. + + If the jsonObject is not already of type JSON_STRING, it is converted to a JSON_STRING, + with any previous contents freed. +*/ void jsonObjectSetString(jsonObject* dest, const char* string) { if(!(dest && string)) return; JSON_INIT_CLEAR(dest, JSON_STRING); @@ -510,6 +872,19 @@ void jsonObjectSetString(jsonObject* dest, const char* string) { a specified numeric string in it. If the string is not numeric, store the equivalent of zero, and return an error status. **/ +/** + @brief Store a copy of a numeric character string in a jsonObject of type JSON_NUMBER. + @param dest Pointer to the jsonObject in which the number will be stored. + @param string Pointer to the numeric string to be stored. + + Both @a dest and @a string must be non-NULL. + + If the jsonObject is not already of type JSON_NUMBER, it is converted to a JSON_STRING, + with any previous contents freed. + + If the input string is not numeric as determined by jsonIsNumeric(), the number stored + is zero. + */ int jsonObjectSetNumberString(jsonObject* dest, const char* string) { if(!(dest && string)) return -1; JSON_INIT_CLEAR(dest, JSON_NUMBER); @@ -524,22 +899,53 @@ int jsonObjectSetNumberString(jsonObject* dest, const char* string) { } } +/** + @brief Store a number in a jsonObject of type JSON_NUMBER. + @param dest Pointer to the jsonObject in which the number will be stored. + @param num The number to be stored. + + If the jsonObject is not already of type JSON_NUMBER, it is converted to one, with any + previous contents freed. +*/ void jsonObjectSetNumber(jsonObject* dest, double num) { if(!dest) return; JSON_INIT_CLEAR(dest, JSON_NUMBER); dest->value.s = doubleToString( num ); } +/** + @brief Assign a class name to a jsonObject. + @param dest Pointer to the jsonObject. + @param classname Pointer to a string containing the class name. + + Both dest and classname must be non-NULL. +*/ void jsonObjectSetClass(jsonObject* dest, const char* classname ) { if(!(dest && classname)) return; free(dest->classname); dest->classname = strdup(classname); } + +/** + @brief Fetch a pointer to the class name of a jsonObject, if any. + @param dest Pointer to the jsonObject. + @return Pointer to a string containing the class name, if there is one; or NULL. + + If not NULL, the pointer returned points to a string stored internally within the + jsonObject. The calling code should @em not try to free it. +*/ const char* jsonObjectGetClass(const jsonObject* dest) { if(!dest) return NULL; return dest->classname; } +/** + @brief Create a copy of an existing jsonObject, including all internal sub-objects. + @param o Pointer to the jsonObject to be copied. + @return A pointer to the newly created copy. + + The calling code is responsible for freeing the copy of the original. +*/ jsonObject* jsonObjectClone( const jsonObject* o ) { if(!o) return jsonNewObject(NULL); @@ -586,6 +992,14 @@ jsonObject* jsonObjectClone( const jsonObject* o ) { return result; } +/** + @brief Return the truth or falsity of a jsonObject of type JSON_BOOL. + @param boolObj Pointer to the jsonObject. + @return 1 or 0, depending on whether the stored boolean is true or false. + + If @a boolObj is NULL, or if it points to a jsonObject not of type JSON_BOOL, the + returned value is zero. +*/ int jsonBoolIsTrue( const jsonObject* boolObj ) { if( boolObj && boolObj->type == JSON_BOOL && boolObj->value.b ) return 1; @@ -593,6 +1007,16 @@ int jsonBoolIsTrue( const jsonObject* boolObj ) { } +/** + @brief Create a copy of the string stored in a jsonObject. + @param o Pointer to the jsonObject whose string is to be copied. + @return Pointer to a newly allocated string copied from the jsonObject. + + If @a o is NULL, or if it points to a jsonObject not of type JSON_STRING or JSON_NUMBER, + the returned value is NULL. In the case of a JSON_NUMBER, the string created is numeric. + + The calling code is responsible for freeing the newly created string. +*/ char* jsonObjectToSimpleString( const jsonObject* o ) { if(!o) return NULL; @@ -616,6 +1040,29 @@ char* jsonObjectToSimpleString( const jsonObject* o ) { This validation follows the rules defined by the grammar at: http://www.json.org/ **/ +/** + @brief Determine whether a specified character string is a valid JSON number. + @param s Pointer to the string to be examined. + @return 1 if the string is numeric, or 0 if not. + + This function defines numericity according to JSON rules; see http://json.org/. This + determination is based purely on the lexical properties of the string. In particular + there is no guarantee that the number in a numeric string is representable in C as a + long long, a long double, or any other built-in type. + + A numeric string consists of: + + - An optional leading minus sign (but not a plus sign) + - One or more decimal digits. The first digit may be a zero only if it is the only + digit to the left of the decimal. + - Optionally, a decimal point followed by one or more decimal digits. + - An optional exponent, consisting of: + - The letter E, in upper or lower case + - An optional plus or minus sign + - One or more decimal digits + + See also jsonScrubNumber(). +*/ int jsonIsNumeric( const char* s ) { if( !s || !*s ) return 0; @@ -699,10 +1146,23 @@ int jsonIsNumeric( const char* s ) { } /** - Allocate and reformat a numeric string into one that is valid - by JSON rules. If the string is not numeric, return NULL. - Caller is responsible for freeing the buffer. - **/ + @brief Edit a string into a valid JSON number, if possible. + @param s Pointer to the string to be edited. + @return A pointer to a newly created numeric string, if possible; otherwise NULL. + + JSON has rather exacting requirements about what constitutes a valid numeric string (see + jsonIsNumeric()). Real-world input may be a bit sloppy. jsonScrubNumber accepts numeric + strings in a less formal format and reformats them, where possible, according to JSON + rules. It removes leading white space, a leading plus sign, and extraneous leading zeros. + It adds a leading zero as needed when the absolute value is less than 1. It also accepts + scientific notation in the form of a bare exponent (e.g. "E-3"), supplying a leading factor + of "1". + + If the input string is non-numeric even according to these relaxed rules, the return value + is NULL. + + The calling code is responsible for freeing the newly created numeric string. +*/ char* jsonScrubNumber( const char* s ) { if( !s || !*s ) return NULL; -- 2.43.2