// vi: ts=4 sw=4 noet: /* ================================================================================== Copyright (c) 2020 Nokia Copyright (c) 2020 AT&T Intellectual Property. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================================================== */ /* Mnemonic: jwrapper.c Abstract: A wrapper interface to the jsmn library which makes it a bit easier to use. Parses a json string capturing the contents in a symtab. This code is based on the AT&T VFd open source library available on github.com/att/vfd. The changes are mostly to port to the RMR version of symtab from VFd's version, however the parsing loop was considerably refactored to eliminate the bad practices in the original code, and to squelch the sonar complaint about a potential, though not valid, NPE. Author: E. Scott Daniels Date: 26 June 2020 */ #include #include #include #include #ifndef DEBUG #define DEBUG 0 #endif #define JSMN_STATIC 1 // jsmn no longer builds into a library; this pulls as static functions #include #include extern void jw_nuke( void* st ); #define JSON_SYM_NAME "_jw_json_string" #define MAX_THINGS 1024 * 4 // max objects/elements #define PT_UNKNOWN 0 // primative types; unknown for non prim #define PT_VALUE 1 #define PT_BOOL 2 #define PT_NULL 3 #define PT_STRING 4 #define OBJ_SPACE 1 // space in the symbol table where json bits are stashed #define MGT_SPACE 2 // non-json objects in the hash (management things) // --------------------------------------------------------------------------------------- /* This is what we will manage in the symtab. Right now we store all values (primatives) as double, but we could be smarter about it and look for a decimal. Unsigned and differences between long, long long etc are tough. */ typedef struct jthing { int jsmn_type; // propigated type from jsmn (jsmn constants) int prim_type; // finer grained primative type (bool, null, value) int nele; // number of elements if applies union { double fv; void *pv; } v; } jthing_t; /* Given the json token, 'extract' the element by marking the end with a nil character, and returning a pointer to the start. We do this so that we don't create a bunch of small buffers that must be found and freed; we can just release the json string and we'll be done (read won't leak). */ static char* extract( char* buf, jsmntok_t *jtoken ) { buf[jtoken->end] = 0; return &buf[jtoken->start]; } #if DEBUG > 0 /* For debugging we should NOT extract as that disrupts the "flow" by adding a nil before the parser gets a chance to actually parse an object. */ static void pull( const char* dest, char* src, jsmntok_t* jtoken ) { memcpy( dest, &src[jtoken->start], jtoken->end - jtoken->start ); dest[jtoken->end - jtoken->start] = 0; } #endif /* create a new jthing and add a reference to it in the symbol table st. sets the number of elements to 1 by default. */ static jthing_t *mk_thing( void *st, char *name, int jsmn_type ) { jthing_t *jtp = NULL; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) malloc( sizeof( *jtp ) )) != NULL ) { if( DEBUG > 1 ) fprintf( stderr, " jwrapper adding: %s type=%d\n", name, jsmn_type ); jtp->jsmn_type = jsmn_type; jtp->prim_type = PT_UNKNOWN; // caller must set this jtp->nele = 1; jtp->v.fv = 0; rmr_sym_put( st, name, OBJ_SPACE, jtp ); } else { fprintf( stderr, "[WARN] jwrapper: unable to create '%s' type=%d\n", name, jsmn_type ); } } return jtp; } /* Find the named array. Returns a pointer to the jthing that represents the array (type, size and pointer to actual array of jthings). Returns nil pointer if the named thing isn't there or isn't an array. */ static jthing_t* suss_array( void* st, const char* name ) { jthing_t* jtp = NULL; // thing that is referenced by the symtab if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) { jtp = jtp->jsmn_type == JSMN_ARRAY ? jtp : NULL; } } return jtp; } /* Suss an array from the hash and return the ith element. */ static jthing_t* suss_element( void* st, const char* name, int idx ) { jthing_t* jtp; // thing that is referenced by the symtab jthing_t* jarray; jthing_t* rv = NULL; if( (jtp = suss_array( st, name )) != NULL // have pointer && idx >= 0 // and in range && idx < jtp->nele && (jarray = jtp->v.pv) != NULL ) { // and exists rv = &jarray[idx]; } return rv; } /* Invoked for each thing in the symtab; we free the things that actually point to allocated data (e.g. arrays) and recurse to handle objects. Only the element passed is used, but this is a prototype which is required by the RMR symtab implementaion, so we play games in the code to keep sonar quiet. */ static void nix_things( void* st, void* se, const char* name, void* ele, void *data ) { jthing_t* j; jthing_t* jarray; int i; if( (j = (jthing_t *) ele) == NULL ) { if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug fprintf( stderr, "jwrapper: nix_thigs: all params were nil\n" ); } return; } switch( j->jsmn_type ) { case JSMN_ARRAY: if( (jarray = (jthing_t *) j->v.pv) != NULL ) { for( i = 0; i < j->nele; i++ ) { // must look for embedded objects if( jarray[i].jsmn_type == JSMN_OBJECT ) { jw_nuke( jarray[i].v.pv ); jarray[i].jsmn_type = JSMN_UNDEFINED; // prevent accidents } } free( j->v.pv ); // must free the array (arrays aren't nested, so all things in the array don't reference allocated mem) free( j ); } break; case JSMN_OBJECT: // delete the sub symtab jw_nuke( j->v.pv ); j->jsmn_type = JSMN_UNDEFINED; // prevent a double free free( j ); break; case JSMN_STRING: case JSMN_PRIMITIVE: free( j ); break; default: break; // more unneeded games to keep sonar complaints away } } /* Nix non-json things that are also in the hash. Silly games played to keep sonar from complaining. This is driven by RMR symtab code which defines the set of params and we use what we need. */ static void nix_mgt( void* st, void* se, const char* name, void* ele, void *data ) { if( ele == NULL ) { if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug fprintf( stderr, "jwrapper: dump_things: all params were nil\n" ); } return; } free( ele ); } /* Invoked for each thing and prints what we can to stderr. Most parms ignored, but symtab code in RMR defines the prototype so they are required. */ static void dump_things( void* st, void* se, const char* name, void* ele, void *data ) { const jthing_t* j; j = (jthing_t *) ele; if( j ) { fprintf( stderr, " jwrapper: element '%s' has ptype %d, jsmn type %d\n", name, j->prim_type, j->jsmn_type ); } else { if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug fprintf( stderr, "jwrapper: dump_things: all params were nil\n" ); } fprintf( stderr, " jwrapper: element has no data: '%s'\n", name ); } } /* Real work for parsing an object ({...}) from the json. Called by jw_new() and recurses to deal with sub-objects. The jsmn parser returns an array of tokens which are expected to be name/data pair. Name must be jsmn string type or we will fail the parse. Data can be any primative type (int,bool) or a complex type (array, object). An array will allow an object as an element, but NOT a nested array; that will cause us to report a failure. */ void* parse_jobject( void* st, char *json, char* prefix ) { jthing_t *jtp; // json thing that we just created int i; int n; char *name; // name in the json char *data; // data string from the json jthing_t* jarray; // array of jthings we'll coonstruct int size; int osize; int njtokens; // tokens actually sussed out jsmn_parser jp; // 'parser' object jsmntok_t *jtokens; // pointer to tokens returned by the parser char pname[1024]; // name with prefix char wbuf[256]; // temp buf to build a working name in char* dstr; // dup'd string int step = 0; // parsing step value to skip tokens picked up int data_idx; // index into tokens for the next bit of data int di; // data skip index for object hopping int stop; // loop index termination point jsmn_init( &jp ); // does this have a failure mode? if( DEBUG ) fprintf( stderr, "> ================= recursion begins =======================\n" ); jtokens = (jsmntok_t *) malloc( sizeof( *jtokens ) * MAX_THINGS ); if( jtokens == NULL ) { fprintf( stderr, "[CRI] jwrapper: cannot allocate tokens array\n" ); return NULL; } if( DEBUG > 1 ) fprintf( stderr, " parsing(%s)\n", json ); njtokens = jsmn_parse( &jp, json, strlen( json ), jtokens, MAX_THINGS ); if( DEBUG ) fprintf( stderr, "> tokens=%d\n", njtokens ); #if DEBUG > 1 for( di = 0; di < njtokens; di++ ) { wbuf[4096]; pull( wbuf, json, &jtokens[di] ); fprintf( stderr, " [%d] t=%d start=%d end=%d (%s)\n", di, jtokens[di].type, jtokens[di].start, jtokens[di].end, wbuf ); } } #endif if( jtokens[0].type != JSMN_OBJECT ) { // if it's not an object then we can't parse it. fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" ); rmr_sym_free( st ); free( jtokens ); return NULL; } i = 1; while( i < njtokens ) { // a final name without data will end up being silently skipped step = 2; // will always need to step over name and data; object and array will add to this if( jtokens[i].type != JSMN_STRING ) { fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n", i, jtokens[i].type, extract( json, &jtokens[i] ) ); rmr_sym_free( st ); free( jtokens ); return NULL; } name = extract( json, &jtokens[i] ); if( DEBUG ) fprintf( stderr, "\n [%d] parsing %s t=%d\n", i, name, jtokens[i].type ); if( *prefix != 0 ) { snprintf( pname, sizeof( pname ), "%s.%s", prefix, name ); name = pname; } size = jtokens[i].size; data_idx = i + 1; // at data token switch( jtokens[data_idx].type ) { case JSMN_OBJECT: // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original) if( DEBUG ) fprintf( stderr, " [%d] %s (object) has %d things\n", data_idx, name, jtokens[data_idx].size ); if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) != NULL && // create thing and reference it in current symtab (jtp->v.pv = (void *) rmr_sym_alloc( 255 ) ) != NULL ) { // object is just a blob dstr = strdup( extract( json, &jtokens[data_idx] ) ); rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, MGT_SPACE, dstr ); // must stash json so it is freed during nuke() parse_jobject( jtp->v.pv, dstr, "" ); // recurse across the object and build a new symtab stop = jtokens[data_idx].end; // calc step; must loop as it's NOT just simple size*2 b/c nested objects are var length if( DEBUG ) fprintf( stderr, " computing step over object elements\n" ); for( di = data_idx+1; di < njtokens-1 && jtokens[di].end < stop ; di++ ) { step++; if( DEBUG ) { fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n", di, jtokens[di].start, jtokens[di].end, extract( json, &jtokens[di]) ); } } } if( DEBUG ) fprintf( stderr, " %s object finished step= %d\n", name, step ); break; case JSMN_ARRAY: size = jtokens[data_idx].size; // size is burried here, NOT with the name if( DEBUG ) fprintf( stderr, " %s is array size=%d\n", name, size ); jtp = mk_thing( st, name, jtokens[data_idx].type ); if( jtp == NULL || data_idx + size > njtokens ) { fprintf( stderr, "[WARN] jwrapper: alloc, or size, error processing element [%d] in json; size=%d ntok=%d\n", i, size, njtokens ); rmr_sym_free( st ); free( jtokens ); return NULL; } data_idx++; // skip first ele; it is the whole array string which I don't grock the need for, but it's their code... jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size ); // allocate the array memset( jarray, 0, sizeof( *jarray ) * size ); jtp->nele = size; for( n = 0; n < size; n++ ) { // for each array element step = 1; // array elements aren't named, so just one initial step if( DEBUG ) fprintf( stderr, "\n parsing [%d] %s element %d of %d\n", data_idx, name, n, size ); jarray[n].prim_type = PT_UNKNOWN; // initially mark as unknown switch( jtokens[data_idx].type ) { case JSMN_OBJECT: jarray[n].v.pv = (void *) rmr_sym_alloc( 255 ); if( DEBUG ) fprintf( stderr, " %s[%d] is object size=%d\n", name, n, jtokens[data_idx].size ); if( jarray[n].v.pv != NULL ) { jarray[n].jsmn_type = JSMN_OBJECT; parse_jobject( jarray[n].v.pv, extract( json, &jtokens[data_idx] ), "" ); // recurse across the object and build a new symtab stop = jtokens[data_idx].end; // same as before, must manually calc step if( DEBUG ) fprintf( stderr, " computing step over object elements start=%d stop=%d\n", data_idx+1, stop ); for( di = data_idx+1; di < njtokens-1 && jtokens[di].end < stop ; di++ ) { step++; if( DEBUG > 1 ) { fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n", di, jtokens[di].start, jtokens[di].end, extract( json, &jtokens[di]) ); } } } if( DEBUG ) fprintf( stderr, " %s[%d] object element finished step= %d\n", name, n, step ); break; case JSMN_ARRAY: fprintf( stderr, "[ERR] jwrapper: %s [%d] array element is not a valid type: nested arrays not supported.\n", name, n ); free( jtp ); free( jarray ); jarray = NULL; free( jtokens ); return NULL; case JSMN_STRING: data = extract( json, &jtokens[data_idx] ); jarray[n].v.pv = (void *) data; jarray[n].prim_type = PT_STRING; jarray[n].jsmn_type = JSMN_STRING; break; case JSMN_PRIMITIVE: data = extract( json, &jtokens[data_idx] ); switch( *data ) { case 'T': case 't': jarray[n].v.fv = 1; jarray[n].prim_type = PT_BOOL; if( DEBUG ) fprintf( stderr, " %s[%d] bool = true\n", name, n ); break; case 'F': case 'f': jarray[n].prim_type = PT_BOOL; jarray[n].v.fv = 0; if( DEBUG ) fprintf( stderr, " %s[%d] bool = false\n", name, n ); break; case 'N': // assume null, nil, or some variant case 'n': jarray[n].prim_type = PT_NULL; jarray[n].v.fv = 0; if( DEBUG ) fprintf( stderr, " %s[%d] null primative\n", name, n ); break; default: jarray[n].prim_type = PT_VALUE; jarray[n].v.fv = strtod( data, NULL ); // store all numerics as double if( DEBUG ) fprintf( stderr, " %s[%d] = %.03f\n", name, n, jarray[n].v.fv ); break; } jarray[n].jsmn_type = JSMN_PRIMITIVE; break; default: if( DEBUG ) fprintf( stderr, "[ERR] jwrapper: %s [%d] array ele is unknown type: %d\n", name, n, jtokens[data_idx].type ); rmr_sym_free( st ); free( jtokens ); return NULL; } if( DEBUG ) fprintf( stderr, " %s[%d] finished, step = %d\n", name, n, step ); data_idx += step; } step = data_idx - i; // data_index has been moved along, so it's a simple subtraction at this point if( DEBUG ) fprintf( stderr, " %s array finished, total step = %d\n", name, step ); break; case JSMN_STRING: data = extract( json, &jtokens[data_idx] ); if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) != NULL ) { jtp->prim_type = PT_STRING; jtp->v.pv = (void *) data; // just point into the large json string } break; case JSMN_PRIMITIVE: data = extract( json, &jtokens[data_idx] ); if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) == NULL ) { break; } switch( *data ) { // assume T|t is true and F|f is false case 'T': case 't': jtp->prim_type = PT_BOOL; jtp->v.fv = 1; break; case 'F': case 'f': jtp->prim_type = PT_BOOL; jtp->v.fv = 0; break; case 'N': // Null or some form of that case 'n': jtp->prim_type = PT_NULL; jtp->v.fv = 0; break; default: jtp->prim_type = PT_VALUE; jtp->v.fv = strtod( data, NULL ); // store all numerics as double break; } break; default: fprintf( stderr, "[WARN] jwrapper: element [%d] is undefined or of unknown type\n", i ); break; } if( DEBUG ) fprintf( stderr, " stepping i = %d by %d max=%d\n", i, step, njtokens ); i += step; // step to next name token } if( DEBUG ) fprintf( stderr, " ================= recursion ends =======================\n\n" ); free( jtokens ); return st; } // --------------- public functions ----------------------------------------------------------------- /* Destroy all operating structures assocaited with the symtab pointer passed in. */ extern void jw_nuke( void* st ) { if( st != NULL ) { rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free any json thing that the symtab references rmr_sym_foreach_class( st, MGT_SPACE, nix_mgt, NULL ); // free management things rmr_sym_free( st ); // free the symtab itself } } /* Scan the given st and write some useful (?) info to stderr. */ extern void jw_dump( void* st ) { if( st != NULL ) { rmr_sym_foreach_class( st, OBJ_SPACE, dump_things, NULL ); } else { fprintf( stderr, " jwrapper: dump: no table\n" ); } } /* Given a json string, parse it, and put the things into a symtab. return the symtab pointer to the caller. They pass the symtab pointer back to the various get functions. This is the entry point. It sets up the symbol table and invokes the parse object funtion to start at the first level. Parse object will recurse for nested objects if present. */ extern void* jw_new( const char* json ) { void *st = NULL; // symbol table char* djson; // dup so we can save it void* rp = NULL; // return value if( json != NULL ) { if( (st = rmr_sym_alloc( MAX_THINGS/4 )) != NULL ) { djson = strdup( json ); // allows user to free/overlay their buffer as needed rp = parse_jobject( st, djson, "" ); // empty prefix for the root object; parse_jo will clean up and free st if( rp == NULL ) { free( djson ); } else { rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, MGT_SPACE, djson ); // must have a reference to the string until symtab is trashed } } } return rp; } /* Returns true (1) if the named field is missing. */ extern int jw_missing( void* st, const char* name ) { int rv = 0; if( st != NULL && name != NULL ) { rv = rmr_sym_get( st, name, OBJ_SPACE ) == NULL; } return rv; } /* Returns true (1) if the named field is in the blob. */ extern int jw_exists( void* st, const char* name ) { int rv = 0; if( st != NULL && name != NULL ) { rv = rmr_sym_get( st, name, OBJ_SPACE ) != NULL; } return rv; } /* Returns true (1) if the primative type is value (double). */ extern int jw_is_value( void* st, const char* name ) { const jthing_t* jtp; // thing that is referenced by the symtab int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) { rv = jtp->prim_type == PT_VALUE; } } return rv; } /* Returns true (1) if the primative type is string. */ extern int jw_is_string( void* st, const char* name ) { const jthing_t* jtp; // thing that is referenced by the symtab int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) { rv = jtp->prim_type == PT_STRING; } } return rv; } /* Returns true (1) if the primative type is boolean. */ extern int jw_is_bool( void* st, const char* name ) { const jthing_t* jtp; // thing that is referenced by the symtab int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) { rv = jtp->prim_type == PT_BOOL; } } return rv; } /* Returns true (1) if the primative type was a 'null' type. */ extern int jw_is_null( void* st, const char* name ) { const jthing_t* jtp; // thing that is referenced by the symtab int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) { rv = jtp->prim_type == PT_NULL; } } return rv; } /* Look up the name in the symtab and return the string (data). */ extern char* jw_string( void* st, const char* name ) { const jthing_t* jtp; // thing that is referenced by the symtab char* rv = NULL; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_STRING ) { rv = (char *) jtp->v.pv; } } return rv; } /* Look up name and return the value. */ extern double jw_value( void* st, const char* name ) { jthing_t* jtp; // thing that is referenced by the symtab double rv = 0.0; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_PRIMITIVE ) { rv = jtp->v.fv; } } return rv; } /* Look up name and return the blob (symtab). */ extern void* jw_blob( void* st, const char* name ) { const jthing_t* jtp; // thing that is referenced by the symtab void* rv = NULL; if( st != NULL && name != NULL ) { if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_OBJECT ) { rv = (void *) jtp->v.pv; } } return rv; } /* Look up the element and return boolean state; This takes the C approach and returns true/false based on the value. */ extern int jw_bool_ele( void* st, const char* name, int idx ) { const jthing_t* jtp; // thing that is referenced by the symtab entry int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL ) { rv = !! ((int) jtp->v.fv); } } return rv; } /* Look up array element as a string. Returns NULL if: name is not an array name is not in the hash index is out of range element is not a string */ extern char* jw_string_ele( void* st, const char* name, int idx ) { const jthing_t* jtp; // thing that is referenced by the symtab entry char* rv = NULL; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL && jtp->jsmn_type == JSMN_STRING ) { rv = (char *) jtp->v.pv; } } return rv; } /* Look up array element as a value. Returns 0 if: name is not an array name is not in the hash index is out of range element is not a value */ extern double jw_value_ele( void* st, const char* name, int idx ) { const jthing_t* jtp; // thing that is referenced by the symtab entry double rv = 0.0; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL && jtp->prim_type == PT_VALUE ) { rv = jtp->v.fv; } } return rv; } /* Look up the element and check to see if it is a string. Return true (1) if it is. */ extern int jw_is_string_ele( void* st, const char* name, int idx ) { jthing_t* jtp; // thing that is referenced by the symtab entry int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL ) { rv = jtp->prim_type == PT_STRING; } } return rv; } /* Look up the element and check to see if it is a value primative. Return true (1) if it is. */ extern int jw_is_value_ele( void* st, const char* name, int idx ) { const jthing_t* jtp; // thing that is referenced by the symtab entry int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL ) { rv = jtp->prim_type == PT_VALUE; } } return rv; } /* Look up the element and check to see if it is a boolean primative. Return true (1) if it is. */ extern int jw_is_bool_ele( void* st, const char* name, int idx ) { const jthing_t* jtp; // thing that is referenced by the symtab entry int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL ) { rv = jtp->prim_type == PT_BOOL; } } return rv; } /* Look up the element and check to see if it is a null primative. Return true (1) if it is. */ extern int jw_is_null_ele( void* st, const char* name, int idx ) { const jthing_t* jtp; // thing that is referenced by the symtab entry int rv = 0; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL ) { rv = jtp->prim_type == PT_NULL; } } return rv; } /* Look up array element as an object. Returns NULL if: name is not an array name is not in the hash index is out of range element is not an object An object in an array is a standalone symbol table. Thus the object is treated differently than a nested object whose members are a part of the parent namespace. An object in an array has its own namespace. */ extern void* jw_obj_ele( void* st, const char* name, int idx ) { const jthing_t* jtp; // thing that is referenced by the symtab entry void* rv = NULL; if( st != NULL && name != NULL ) { if( (jtp = suss_element( st, name, idx )) != NULL && jtp->jsmn_type == JSMN_OBJECT ) { rv = (void *) jtp->v.pv; } } return rv; } /* Return the size of the array named. Returns -1 if the thing isn't an array, and returns the number of elements otherwise. */ extern int jw_array_len( void* st, const char* name ) { const jthing_t* jtp; // thing that is referenced by the symtab entry int rv = -1; if( st != NULL && name != NULL ) { if( (jtp = suss_array( st, name )) != NULL ) { rv = jtp->nele; } } return rv; }