3 ==================================================================================
4 Copyright (c) 2020 Nokia
5 Copyright (c) 2020 AT&T Intellectual Property.
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
11 http://www.apache.org/licenses/LICENSE-2.0
13 Unless required by applicable law or agreed to in writing, software
14 distributed under the License is distributed on an "AS IS" BASIS,
15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 See the License for the specific language governing permissions and
17 limitations under the License.
18 ==================================================================================
23 Abstract: A wrapper interface to the jsmn library which makes it a bit easier
24 to use. Parses a json string capturing the contents in a symtab.
26 This code is based on the AT&T VFd open source library available
27 on github.com/att/vfd. The changes are mostly to port to the
28 RMR version of symtab from VFd's version, however the parsing loop
29 was considerably refactored to eliminate the bad practices in the
30 original code, and to squelch the sonar complaint about a potential,
31 though not valid, NPE.
33 Author: E. Scott Daniels
47 #define JSMN_STATIC 1 // jsmn no longer builds into a library; this pulls as static functions
50 #include <rmr/rmr_symtab.h>
52 extern void jw_nuke( void* st );
54 #define JSON_SYM_NAME "_jw_json_string"
55 #define MAX_THINGS 1024 * 4 // max objects/elements
57 #define PT_UNKNOWN 0 // primative types; unknown for non prim
63 #define OBJ_SPACE 1 // space in the symbol table where json bits are stashed
64 #define MGT_SPACE 2 // non-json objects in the hash (management things)
67 // ---------------------------------------------------------------------------------------
70 This is what we will manage in the symtab. Right now we store all values (primatives)
71 as double, but we could be smarter about it and look for a decimal. Unsigned and
72 differences between long, long long etc are tough.
74 typedef struct jthing {
75 int jsmn_type; // propigated type from jsmn (jsmn constants)
76 int prim_type; // finer grained primative type (bool, null, value)
77 int nele; // number of elements if applies
86 Given the json token, 'extract' the element by marking the end with a
87 nil character, and returning a pointer to the start. We do this so that
88 we don't create a bunch of small buffers that must be found and freed; we
89 can just release the json string and we'll be done (read won't leak).
91 static char* extract( char* buf, jsmntok_t *jtoken ) {
93 return &buf[jtoken->start];
98 For debugging we should NOT extract as that disrupts the "flow" by
99 adding a nil before the parser gets a chance to actually parse an
102 static void pull( const char* dest, char* src, jsmntok_t* jtoken ) {
103 memcpy( dest, &src[jtoken->start], jtoken->end - jtoken->start );
104 dest[jtoken->end - jtoken->start] = 0;
109 create a new jthing and add a reference to it in the symbol table st.
110 sets the number of elements to 1 by default.
112 static jthing_t *mk_thing( void *st, char *name, int jsmn_type ) {
113 jthing_t *jtp = NULL;
115 if( st != NULL && name != NULL ) {
116 if( (jtp = (jthing_t *) malloc( sizeof( *jtp ) )) != NULL ) {
118 if( DEBUG > 1 ) fprintf( stderr, "<DBUG> jwrapper adding: %s type=%d\n", name, jsmn_type );
120 jtp->jsmn_type = jsmn_type;
121 jtp->prim_type = PT_UNKNOWN; // caller must set this
125 rmr_sym_put( st, name, OBJ_SPACE, jtp );
127 fprintf( stderr, "[WARN] jwrapper: unable to create '%s' type=%d\n", name, jsmn_type );
136 Find the named array. Returns a pointer to the jthing that represents
137 the array (type, size and pointer to actual array of jthings).
138 Returns nil pointer if the named thing isn't there or isn't an array.
140 static jthing_t* suss_array( void* st, const char* name ) {
141 jthing_t* jtp = NULL; // thing that is referenced by the symtab
143 if( st != NULL && name != NULL ) {
144 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
145 jtp = jtp->jsmn_type == JSMN_ARRAY ? jtp : NULL;
153 Suss an array from the hash and return the ith element.
155 static jthing_t* suss_element( void* st, const char* name, int idx ) {
156 jthing_t* jtp; // thing that is referenced by the symtab
160 if( (jtp = suss_array( st, name )) != NULL // have pointer
161 && idx >= 0 // and in range
163 && (jarray = jtp->v.pv) != NULL ) { // and exists
173 Invoked for each thing in the symtab; we free the things that actually point to
174 allocated data (e.g. arrays) and recurse to handle objects.
176 Only the element passed is used, but this is a prototype which is required by
177 the RMR symtab implementaion, so we play games in the code to keep sonar quiet.
179 static void nix_things( void* st, void* se, const char* name, void* ele, void *data ) {
184 if( (j = (jthing_t *) ele) == NULL ) {
185 if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug
186 fprintf( stderr, "jwrapper: nix_thigs: all params were nil\n" );
191 switch( j->jsmn_type ) {
193 if( (jarray = (jthing_t *) j->v.pv) != NULL ) {
194 for( i = 0; i < j->nele; i++ ) { // must look for embedded objects
195 if( jarray[i].jsmn_type == JSMN_OBJECT ) {
196 jw_nuke( jarray[i].v.pv );
197 jarray[i].jsmn_type = JSMN_UNDEFINED; // prevent accidents
201 free( j->v.pv ); // must free the array (arrays aren't nested, so all things in the array don't reference allocated mem)
206 case JSMN_OBJECT: // delete the sub symtab
208 j->jsmn_type = JSMN_UNDEFINED; // prevent a double free
218 break; // more unneeded games to keep sonar complaints away
223 Nix non-json things that are also in the hash.
225 Silly games played to keep sonar from complaining. This is driven by RMR
226 symtab code which defines the set of params and we use what we need.
228 static void nix_mgt( void* st, void* se, const char* name, void* ele, void *data ) {
231 if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug
232 fprintf( stderr, "jwrapper: dump_things: all params were nil\n" );
242 Invoked for each thing and prints what we can to stderr.
243 Most parms ignored, but symtab code in RMR defines the prototype so they are required.
245 static void dump_things( void* st, void* se, const char* name, void* ele, void *data ) {
248 j = (jthing_t *) ele;
250 fprintf( stderr, "<DBUG> jwrapper: element '%s' has ptype %d, jsmn type %d\n", name, j->prim_type, j->jsmn_type );
252 if( st == NULL && name == NULL && se == NULL && data == NULL ) { // these are ignored, but this keeps sonar from screaming bug
253 fprintf( stderr, "jwrapper: dump_things: all params were nil\n" );
255 fprintf( stderr, "<DBUG> jwrapper: element has no data: '%s'\n", name );
260 Real work for parsing an object ({...}) from the json. Called by jw_new() and
261 recurses to deal with sub-objects.
263 The jsmn parser returns an array of tokens which are expected to be name/data
264 pair. Name must be jsmn string type or we will fail the parse. Data can be
265 any primative type (int,bool) or a complex type (array, object). An array
266 will allow an object as an element, but NOT a nested array; that will cause us
269 void* parse_jobject( void* st, char *json, char* prefix ) {
270 jthing_t *jtp; // json thing that we just created
273 char *name; // name in the json
274 char *data; // data string from the json
275 jthing_t* jarray; // array of jthings we'll coonstruct
278 int njtokens; // tokens actually sussed out
279 jsmn_parser jp; // 'parser' object
280 jsmntok_t *jtokens; // pointer to tokens returned by the parser
281 char pname[1024]; // name with prefix
282 char wbuf[256]; // temp buf to build a working name in
283 char* dstr; // dup'd string
284 int step = 0; // parsing step value to skip tokens picked up
285 int data_idx; // index into tokens for the next bit of data
286 int di; // data skip index for object hopping
287 int stop; // loop index termination point
289 jsmn_init( &jp ); // does this have a failure mode?
291 if( DEBUG ) fprintf( stderr, "<DBUG>> ================= recursion begins =======================\n" );
292 jtokens = (jsmntok_t *) malloc( sizeof( *jtokens ) * MAX_THINGS );
293 if( jtokens == NULL ) {
294 fprintf( stderr, "[CRI] jwrapper: cannot allocate tokens array\n" );
299 if( DEBUG > 1 ) fprintf( stderr, "<DBUG> parsing(%s)\n", json );
300 njtokens = jsmn_parse( &jp, json, strlen( json ), jtokens, MAX_THINGS );
302 if( DEBUG ) fprintf( stderr, "<DBUG>> tokens=%d\n", njtokens );
304 for( di = 0; di < njtokens; di++ ) {
306 pull( wbuf, json, &jtokens[di] );
307 fprintf( stderr, "<DBUG> [%d] t=%d start=%d end=%d (%s)\n",
308 di, jtokens[di].type, jtokens[di].start, jtokens[di].end, wbuf );
313 if( jtokens[0].type != JSMN_OBJECT ) { // if it's not an object then we can't parse it.
314 fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" );
321 while( i < njtokens ) { // a final name without data will end up being silently skipped
322 step = 2; // will always need to step over name and data; object and array will add to this
324 if( jtokens[i].type != JSMN_STRING ) {
325 fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n",
326 i, jtokens[i].type, extract( json, &jtokens[i] ) );
331 name = extract( json, &jtokens[i] );
332 if( DEBUG ) fprintf( stderr, "\n<DBUG> [%d] parsing %s t=%d\n", i, name, jtokens[i].type );
334 snprintf( pname, sizeof( pname ), "%s.%s", prefix, name );
338 size = jtokens[i].size;
340 data_idx = i + 1; // at data token
341 switch( jtokens[data_idx].type ) {
342 case JSMN_OBJECT: // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original)
343 if( DEBUG ) fprintf( stderr, "<DBUG> [%d] %s (object) has %d things\n", data_idx, name, jtokens[data_idx].size );
345 if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) != NULL && // create thing and reference it in current symtab
346 (jtp->v.pv = (void *) rmr_sym_alloc( 255 ) ) != NULL ) { // object is just a blob
348 dstr = strdup( extract( json, &jtokens[data_idx] ) );
349 rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, MGT_SPACE, dstr ); // must stash json so it is freed during nuke()
350 parse_jobject( jtp->v.pv, dstr, "" ); // recurse across the object and build a new symtab
352 stop = jtokens[data_idx].end; // calc step; must loop as it's NOT just simple size*2 b/c nested objects are var length
353 if( DEBUG ) fprintf( stderr, "<DBUG> computing step over object elements\n" );
354 for( di = data_idx+1; di < njtokens-1 && jtokens[di].end < stop ; di++ ) {
357 fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
358 di, jtokens[di].start, jtokens[di].end, extract( json, &jtokens[di]) );
362 if( DEBUG ) fprintf( stderr, "<DBUG> %s object finished step= %d\n", name, step );
366 size = jtokens[data_idx].size; // size is burried here, NOT with the name
367 if( DEBUG ) fprintf( stderr, "<DBUG> %s is array size=%d\n", name, size );
368 jtp = mk_thing( st, name, jtokens[data_idx].type );
370 if( jtp == NULL || data_idx + size > njtokens ) {
371 fprintf( stderr, "[WARN] jwrapper: alloc, or size, error processing element [%d] in json; size=%d ntok=%d\n",
378 data_idx++; // skip first ele; it is the whole array string which I don't grock the need for, but it's their code...
379 jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size ); // allocate the array
380 memset( jarray, 0, sizeof( *jarray ) * size );
383 for( n = 0; n < size; n++ ) { // for each array element
384 step = 1; // array elements aren't named, so just one initial step
386 if( DEBUG ) fprintf( stderr, "\n<DBUG> parsing [%d] %s element %d of %d\n", data_idx, name, n, size );
387 jarray[n].prim_type = PT_UNKNOWN; // initially mark as unknown
389 switch( jtokens[data_idx].type ) {
391 jarray[n].v.pv = (void *) rmr_sym_alloc( 255 );
392 if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] is object size=%d\n", name, n, jtokens[data_idx].size );
393 if( jarray[n].v.pv != NULL ) {
394 jarray[n].jsmn_type = JSMN_OBJECT;
395 parse_jobject( jarray[n].v.pv, extract( json, &jtokens[data_idx] ), "" ); // recurse across the object and build a new symtab
396 stop = jtokens[data_idx].end; // same as before, must manually calc step
397 if( DEBUG ) fprintf( stderr, "<DBUG> computing step over object elements start=%d stop=%d\n", data_idx+1, stop );
398 for( di = data_idx+1; di < njtokens-1 && jtokens[di].end < stop ; di++ ) {
401 fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
402 di, jtokens[di].start, jtokens[di].end, extract( json, &jtokens[di]) );
406 if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] object element finished step= %d\n", name, n, step );
410 fprintf( stderr, "[ERR] jwrapper: %s [%d] array element is not a valid type: nested arrays not supported.\n", name, n );
418 data = extract( json, &jtokens[data_idx] );
419 jarray[n].v.pv = (void *) data;
420 jarray[n].prim_type = PT_STRING;
421 jarray[n].jsmn_type = JSMN_STRING;
425 data = extract( json, &jtokens[data_idx] );
430 jarray[n].prim_type = PT_BOOL;
431 if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] bool = true\n", name, n );
436 jarray[n].prim_type = PT_BOOL;
438 if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] bool = false\n", name, n );
441 case 'N': // assume null, nil, or some variant
443 jarray[n].prim_type = PT_NULL;
445 if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] null primative\n", name, n );
449 jarray[n].prim_type = PT_VALUE;
450 jarray[n].v.fv = strtod( data, NULL ); // store all numerics as double
451 if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] = %.03f\n", name, n, jarray[n].v.fv );
455 jarray[n].jsmn_type = JSMN_PRIMITIVE;
459 if( DEBUG ) fprintf( stderr, "[ERR] jwrapper: %s [%d] array ele is unknown type: %d\n",
460 name, n, jtokens[data_idx].type );
466 if( DEBUG ) fprintf( stderr, "<DBUG> %s[%d] finished, step = %d\n", name, n, step );
470 step = data_idx - i; // data_index has been moved along, so it's a simple subtraction at this point
471 if( DEBUG ) fprintf( stderr, "<DBUG> %s array finished, total step = %d\n", name, step );
475 data = extract( json, &jtokens[data_idx] );
476 if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) != NULL ) {
477 jtp->prim_type = PT_STRING;
478 jtp->v.pv = (void *) data; // just point into the large json string
483 data = extract( json, &jtokens[data_idx] );
485 if( (jtp = mk_thing( st, name, jtokens[data_idx].type )) == NULL ) {
488 switch( *data ) { // assume T|t is true and F|f is false
491 jtp->prim_type = PT_BOOL;
497 jtp->prim_type = PT_BOOL;
501 case 'N': // Null or some form of that
503 jtp->prim_type = PT_NULL;
508 jtp->prim_type = PT_VALUE;
509 jtp->v.fv = strtod( data, NULL ); // store all numerics as double
515 fprintf( stderr, "[WARN] jwrapper: element [%d] is undefined or of unknown type\n", i );
519 if( DEBUG ) fprintf( stderr, "<DBUG> stepping i = %d by %d max=%d\n", i, step, njtokens );
520 i += step; // step to next name token
523 if( DEBUG ) fprintf( stderr, "<DBUG> ================= recursion ends =======================\n\n" );
528 // --------------- public functions -----------------------------------------------------------------
531 Destroy all operating structures assocaited with the symtab pointer passed in.
533 extern void jw_nuke( void* st ) {
535 rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free any json thing that the symtab references
536 rmr_sym_foreach_class( st, MGT_SPACE, nix_mgt, NULL ); // free management things
537 rmr_sym_free( st ); // free the symtab itself
542 Scan the given st and write some useful (?) info to stderr.
544 extern void jw_dump( void* st ) {
546 rmr_sym_foreach_class( st, OBJ_SPACE, dump_things, NULL );
548 fprintf( stderr, "<DBUG> jwrapper: dump: no table\n" );
553 Given a json string, parse it, and put the things into a symtab.
554 return the symtab pointer to the caller. They pass the symtab
555 pointer back to the various get functions.
557 This is the entry point. It sets up the symbol table and invokes the parse object
558 funtion to start at the first level. Parse object will recurse for nested objects
561 extern void* jw_new( const char* json ) {
562 void *st = NULL; // symbol table
563 char* djson; // dup so we can save it
564 void* rp = NULL; // return value
567 if( (st = rmr_sym_alloc( MAX_THINGS/4 )) != NULL ) {
568 djson = strdup( json ); // allows user to free/overlay their buffer as needed
569 rp = parse_jobject( st, djson, "" ); // empty prefix for the root object; parse_jo will clean up and free st
573 rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, MGT_SPACE, djson ); // must have a reference to the string until symtab is trashed
582 Returns true (1) if the named field is missing.
584 extern int jw_missing( void* st, const char* name ) {
587 if( st != NULL && name != NULL ) {
588 rv = rmr_sym_get( st, name, OBJ_SPACE ) == NULL;
595 Returns true (1) if the named field is in the blob.
597 extern int jw_exists( void* st, const char* name ) {
600 if( st != NULL && name != NULL ) {
601 rv = rmr_sym_get( st, name, OBJ_SPACE ) != NULL;
608 Returns true (1) if the primative type is value (double).
610 extern int jw_is_value( void* st, const char* name ) {
611 const jthing_t* jtp; // thing that is referenced by the symtab
614 if( st != NULL && name != NULL ) {
615 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
616 rv = jtp->prim_type == PT_VALUE;
623 Returns true (1) if the primative type is string.
625 extern int jw_is_string( void* st, const char* name ) {
626 const jthing_t* jtp; // thing that is referenced by the symtab
629 if( st != NULL && name != NULL ) {
630 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
631 rv = jtp->prim_type == PT_STRING;
639 Returns true (1) if the primative type is boolean.
641 extern int jw_is_bool( void* st, const char* name ) {
642 const jthing_t* jtp; // thing that is referenced by the symtab
645 if( st != NULL && name != NULL ) {
646 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
647 rv = jtp->prim_type == PT_BOOL;
655 Returns true (1) if the primative type was a 'null' type.
657 extern int jw_is_null( void* st, const char* name ) {
658 const jthing_t* jtp; // thing that is referenced by the symtab
661 if( st != NULL && name != NULL ) {
662 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
663 rv = jtp->prim_type == PT_NULL;
671 Look up the name in the symtab and return the string (data).
673 extern char* jw_string( void* st, const char* name ) {
674 const jthing_t* jtp; // thing that is referenced by the symtab
677 if( st != NULL && name != NULL ) {
678 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_STRING ) {
679 rv = (char *) jtp->v.pv;
687 Look up name and return the value.
689 extern double jw_value( void* st, const char* name ) {
690 jthing_t* jtp; // thing that is referenced by the symtab
693 if( st != NULL && name != NULL ) {
694 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_PRIMITIVE ) {
704 Look up name and return the blob (symtab).
706 extern void* jw_blob( void* st, const char* name ) {
707 const jthing_t* jtp; // thing that is referenced by the symtab
710 if( st != NULL && name != NULL ) {
711 if( (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL && jtp->jsmn_type == JSMN_OBJECT ) {
712 rv = (void *) jtp->v.pv;
720 Look up the element and return boolean state; This takes the C approach and
721 returns true/false based on the value.
723 extern int jw_bool_ele( void* st, const char* name, int idx ) {
724 const jthing_t* jtp; // thing that is referenced by the symtab entry
727 if( st != NULL && name != NULL ) {
728 if( (jtp = suss_element( st, name, idx )) != NULL ) {
729 rv = !! ((int) jtp->v.fv);
736 Look up array element as a string. Returns NULL if:
738 name is not in the hash
739 index is out of range
740 element is not a string
742 extern char* jw_string_ele( void* st, const char* name, int idx ) {
743 const jthing_t* jtp; // thing that is referenced by the symtab entry
746 if( st != NULL && name != NULL ) {
747 if( (jtp = suss_element( st, name, idx )) != NULL && jtp->jsmn_type == JSMN_STRING ) {
748 rv = (char *) jtp->v.pv;
756 Look up array element as a value. Returns 0 if:
758 name is not in the hash
759 index is out of range
760 element is not a value
762 extern double jw_value_ele( void* st, const char* name, int idx ) {
763 const jthing_t* jtp; // thing that is referenced by the symtab entry
766 if( st != NULL && name != NULL ) {
767 if( (jtp = suss_element( st, name, idx )) != NULL && jtp->prim_type == PT_VALUE ) {
775 Look up the element and check to see if it is a string.
776 Return true (1) if it is.
778 extern int jw_is_string_ele( void* st, const char* name, int idx ) {
779 jthing_t* jtp; // thing that is referenced by the symtab entry
782 if( st != NULL && name != NULL ) {
783 if( (jtp = suss_element( st, name, idx )) != NULL ) {
784 rv = jtp->prim_type == PT_STRING;
792 Look up the element and check to see if it is a value primative.
793 Return true (1) if it is.
795 extern int jw_is_value_ele( void* st, const char* name, int idx ) {
796 const jthing_t* jtp; // thing that is referenced by the symtab entry
799 if( st != NULL && name != NULL ) {
800 if( (jtp = suss_element( st, name, idx )) != NULL ) {
801 rv = jtp->prim_type == PT_VALUE;
809 Look up the element and check to see if it is a boolean primative.
810 Return true (1) if it is.
812 extern int jw_is_bool_ele( void* st, const char* name, int idx ) {
813 const jthing_t* jtp; // thing that is referenced by the symtab entry
816 if( st != NULL && name != NULL ) {
817 if( (jtp = suss_element( st, name, idx )) != NULL ) {
818 rv = jtp->prim_type == PT_BOOL;
826 Look up the element and check to see if it is a null primative.
827 Return true (1) if it is.
829 extern int jw_is_null_ele( void* st, const char* name, int idx ) {
830 const jthing_t* jtp; // thing that is referenced by the symtab entry
833 if( st != NULL && name != NULL ) {
834 if( (jtp = suss_element( st, name, idx )) != NULL ) {
835 rv = jtp->prim_type == PT_NULL;
843 Look up array element as an object. Returns NULL if:
845 name is not in the hash
846 index is out of range
847 element is not an object
849 An object in an array is a standalone symbol table. Thus the object
850 is treated differently than a nested object whose members are a
851 part of the parent namespace. An object in an array has its own
854 extern void* jw_obj_ele( void* st, const char* name, int idx ) {
855 const jthing_t* jtp; // thing that is referenced by the symtab entry
858 if( st != NULL && name != NULL ) {
859 if( (jtp = suss_element( st, name, idx )) != NULL && jtp->jsmn_type == JSMN_OBJECT ) {
860 rv = (void *) jtp->v.pv;
868 Return the size of the array named. Returns -1 if the thing isn't an array,
869 and returns the number of elements otherwise.
871 extern int jw_array_len( void* st, const char* name ) {
872 const jthing_t* jtp; // thing that is referenced by the symtab entry
875 if( st != NULL && name != NULL ) {
876 if( (jtp = suss_array( st, name )) != NULL ) {