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.
30 Author: E. Scott Daniels
44 #define JSMN_STATIC 1 // jsmn no longer builds into a library; this pulls as static functions
46 //#include <../../ext/jsmn/jsmn.h>
48 #include <rmr/rmr_symtab.h>
50 #define JSON_SYM_NAME "_jw_json_string"
51 #define MAX_THINGS 1024 * 4 // max objects/elements
53 #define PT_UNKNOWN 0 // primative types; unknown for non prim
59 #define OBJ_SPACE 1 // space in the symbol table where json bits are stashed
60 #define MGT_SPACE 2 // non-json objects in the hash (management things)
62 extern void jw_nuke( void* st );
64 // ---------------------------------------------------------------------------------------
67 This is what we will manage in the symtab. Right now we store all values (primatives)
68 as double, but we could be smarter about it and look for a decimal. Unsigned and
69 differences between long, long long etc are tough.
71 typedef struct jthing {
72 int jsmn_type; // propigated type from jsmn (jsmn constants)
73 int prim_type; // finer grained primative type (bool, null, value)
74 int nele; // number of elements if applies
83 Given the json token, 'extract' the element by marking the end with a
84 nil character, and returning a pointer to the start. We do this so that
85 we don't create a bunch of small buffers that must be found and freed; we
86 can just release the json string and we'll be done (read won't leak).
88 static char* extract( char* buf, jsmntok_t *jtoken ) {
90 return &buf[jtoken->start];
94 create a new jthing and add a reference to it in the symbol table st.
95 sets the number of elements to 1 by default.
97 static jthing_t *mk_thing( void *st, char *name, int jsmn_type ) {
102 (jtp = (jthing_t *) malloc( sizeof( *jtp ) )) != NULL ) {
105 fprintf( stderr, "<DBUG> jwrapper adding: %s type=%d\n", name, jsmn_type );
108 jtp->jsmn_type = jsmn_type;
109 jtp->prim_type = PT_UNKNOWN; // caller must set this
113 rmr_sym_put( st, name, OBJ_SPACE, jtp );
115 fprintf( stderr, "[WARN] jwrapper: unable to create '%s' type=%d\n", name, jsmn_type );
123 Find the named array. Returns a pointer to the jthing that represents
124 the array (type, size and pointer to actual array of jthings).
125 Returns nil pointer if the named thing isn't there or isn't an array.
127 static jthing_t* suss_array( void* st, const char* name ) {
128 jthing_t* jtp = NULL; // thing that is referenced by the symtab
132 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
134 jtp = jtp->jsmn_type == JSMN_ARRAY ? jtp : NULL;
141 Suss an array from the hash and return the ith element.
143 static jthing_t* suss_element( void* st, const char* name, int idx ) {
144 jthing_t* jtp; // thing that is referenced by the symtab
148 if( (jtp = suss_array( st, name )) != NULL && // have pointer
149 idx >= 0 && // and in range
152 if( (jarray = jtp->v.pv) != NULL ) {
162 Invoked for each thing in the symtab; we free the things that actually point to
163 allocated data (e.g. arrays) and recurse to handle objects.
165 static void nix_things( void* st, void* se, const char* name, void* ele, void *data ) {
170 j = (jthing_t *) ele;
172 switch( j->jsmn_type ) {
174 if( (jarray = (jthing_t *) j->v.pv) != NULL ) {
175 for( i = 0; i < j->nele; i++ ) { // must look for embedded objects
176 if( jarray[i].jsmn_type == JSMN_OBJECT ) {
177 jw_nuke( jarray[i].v.pv );
178 jarray[i].jsmn_type = JSMN_UNDEFINED; // prevent accidents
182 free( j->v.pv ); // must free the array (arrays aren't nested, so all things in the array don't reference allocated mem)
187 case JSMN_OBJECT: // delete the sub symtab
189 j->jsmn_type = JSMN_UNDEFINED; // prevent a double free
202 Nix non-json things that are also in the hash.
204 static void nix_mgt( void* st, void* se, const char* name, void* ele, void *data ) {
209 Invoked for each thing and prints what we can to stderr.
211 static void dump_things( void* st, void* se, const char* name, void* ele, void *data ) {
216 j = (jthing_t *) ele;
218 fprintf( stderr, "<DBUG> jwrapper: element '%s' has ptype %d, jsmn type %d\n", name, j->prim_type, j->jsmn_type );
220 fprintf( stderr, "<DBUG> jwrapper: element has no data: '%s'\n", name );
225 Real work for parsing an object ({...}) from the json. Called by jw_new() and
226 recurses to deal with sub-objects.
228 void* parse_jobject( void* st, char *json, char* prefix ) {
229 jthing_t *jtp; // json thing that we just created
232 char *name; // name in the json
233 char *data; // data string from the json
234 jthing_t* jarray; // array of jthings we'll coonstruct
237 int njtokens; // tokens actually sussed out
238 jsmn_parser jp; // 'parser' object
239 jsmntok_t *jtokens; // pointer to tokens returned by the parser
240 char pname[1024]; // name with prefix
241 char wbuf[256]; // temp buf to build a working name in
242 char* dstr; // dup'd string
244 jsmn_init( &jp ); // does this have a failure mode?
246 jtokens = (jsmntok_t *) malloc( sizeof( *jtokens ) * MAX_THINGS );
247 if( jtokens == NULL ) {
248 fprintf( stderr, "[CRI] jwrapper: cannot allocate tokens array\n" );
252 njtokens = jsmn_parse( &jp, json, strlen( json ), jtokens, MAX_THINGS );
254 if( jtokens[0].type != JSMN_OBJECT ) { // if it's not an object then we can't parse it.
255 fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" );
262 for( i = 1; i < njtokens-1; i++ ) {
263 fprintf( stderr, "<DBUG> %4d: size=%d start=%d end=%d %s\n", i, jtokens[i].size, jtokens[i].start, jtokens[i].end, extract( json, &jtokens[i] ) );
267 for( i = 1; i < njtokens-1; i++ ) { // we'll silently skip the last token if it's "name" without a value
268 if( jtokens[i].type != JSMN_STRING ) {
269 fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n", i, jtokens[i].type, extract( json, &jtokens[i] ) );
274 name = extract( json, &jtokens[i] );
276 snprintf( pname, sizeof( pname ), "%s.%s", prefix, name );
280 size = jtokens[i].size;
282 i++; // at the data token now
283 switch( jtokens[i].type ) {
284 case JSMN_OBJECT: // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original)
285 dstr = strdup( extract( json, &jtokens[i] ) );
286 snprintf( wbuf, sizeof( wbuf ), "%s_json", name ); // must stash the json string in the symtab for clean up during nuke
287 rmr_sym_put( st, wbuf, MGT_SPACE, dstr );
289 parse_jobject( st, dstr, name ); // recurse to add the object as objectname.xxxx elements
291 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL && // create thing and reference it in current symtab
292 (jtp->v.pv = (void *) rmr_sym_alloc( 255 ) ) != NULL ) { // object is just a blob
294 dstr = strdup( extract( json, &jtokens[i] ) );
295 rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, MGT_SPACE, dstr ); // must stash json so it is freed during nuke()
296 parse_jobject( jtp->v.pv, dstr, "" ); // recurse acorss the string and build a new symtab
298 size = jtokens[i].end; // done with them, we need to skip them
300 while( i < njtokens-1 && jtokens[i].end < size ) {
302 fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
303 i, jtokens[i].start, jtokens[i].end, extract( json, &jtokens[i]) );
308 i--; // must allow loop to bump past the last
313 size = jtokens[i].size; // size is burried here, and not with the name
314 jtp = mk_thing( st, name, jtokens[i].type );
316 i++; // skip first ele; it is the whole array string which I don't grock the need for, but it's their code...
318 fprintf( stderr, "[WARN] jwrapper: memory alloc error processing element [%d] in json\n", i );
323 jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size ); // allocate the array
324 memset( jarray, 0, sizeof( *jarray ) * size );
327 for( n = 0; n < size; n++ ) { // for each array element
328 jarray[n].prim_type = PT_UNKNOWN; // assume not primative type
329 switch( jtokens[i+n].type ) {
332 jarray[n].v.pv = (void *) rmr_sym_alloc( 255 );
333 if( jarray[n].v.pv != NULL ) {
334 jarray[n].jsmn_type = JSMN_OBJECT;
335 parse_jobject( jarray[n].v.pv, extract( json, &jtokens[i+n] ), "" ); // recurse acorss the string and build a new symtab
336 osize = jtokens[i+n].end; // done with them, we need to skip them
338 while( i+n < njtokens-1 && jtokens[n+i].end < osize ) {
341 i--; // allow incr at loop end
346 fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (array) is not string or primative\n", i, n );
347 n += jtokens[i+n].size; // this should skip the nested array
354 data = extract( json, &jtokens[i+n] );
355 jarray[n].v.pv = (void *) data;
356 jarray[n].prim_type = PT_STRING;
357 jarray[n].jsmn_type = JSMN_STRING;
361 data = extract( json, &jtokens[i+n] );
366 jarray[n].prim_type = PT_BOOL;
371 jarray[n].prim_type = PT_BOOL;
375 case 'N': // assume null, nil, or some variant
377 jarray[n].prim_type = PT_NULL;
382 jarray[n].prim_type = PT_VALUE;
383 jarray[n].v.fv = strtod( data, NULL ); // store all numerics as double
387 jarray[n].jsmn_type = JSMN_PRIMITIVE;
393 fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (unknown=%d) is not string or primative\n", i, n, jtokens[i].type );
401 i += size - 1; // must allow loop to push to next
405 data = extract( json, &jtokens[i] );
406 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
407 jtp->prim_type = PT_STRING;
408 jtp->v.pv = (void *) data; // just point into the large json string
413 data = extract( json, &jtokens[i] );
414 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
415 switch( *data ) { // assume T|t is true and F|f is false
418 jtp->prim_type = PT_BOOL;
424 jtp->prim_type = PT_BOOL;
428 case 'N': // Null or some form of that
430 jtp->prim_type = PT_NULL;
435 jtp->prim_type = PT_VALUE;
436 jtp->v.fv = strtod( data, NULL ); // store all numerics as double
443 fprintf( stderr, "[WARN] jwrapper: element [%d] is undefined or of unknown type\n", i );
452 // --------------- public functions -----------------------------------------------------------------
455 Destroy all operating structures assocaited with the symtab pointer passed in.
457 extern void jw_nuke( void* st ) {
458 char* buf; // pointer to the original json to free
461 rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free any json thing that the symtab references
462 rmr_sym_foreach_class( st, MGT_SPACE, nix_mgt, NULL ); // free management things
463 rmr_sym_free( st ); // free the symtab itself
468 Scan the given st and write some useful (?) info to stderr.
470 extern void jw_dump( void* st ) {
472 rmr_sym_foreach_class( st, OBJ_SPACE, dump_things, NULL );
474 fprintf( stderr, "<DBUG> jwrapper: dump: no table\n" );
479 Given a json string, parse it, and put the things into a symtab.
480 return the symtab pointer to the caller. They pass the symtab
481 pointer back to the various get functions.
483 This is the entry point. It sets up the symbol table and invokes the parse object
484 funtion to start at the first level. Parse object will recurse for nested objects
487 extern void* jw_new( const char* json ) {
488 void *st = NULL; // symbol table
489 char* djson; // dup so we can save it
490 void* rp = NULL; // return value
492 if( json != NULL && (st = rmr_sym_alloc( MAX_THINGS/4 )) != NULL ) {
493 djson = strdup( json ); // allows user to free/overlay their buffer as needed
494 rp = parse_jobject( st, djson, "" ); // empty prefix for the root object; parse_jo will clean up and free st
498 rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, MGT_SPACE, djson ); // must have a reference to the string until symtab is trashed
506 Returns true (1) if the named field is missing.
508 extern int jw_missing( void* st, const char* name ) {
511 if( st != NULL && name != NULL ) {
512 rv = rmr_sym_get( st, name, OBJ_SPACE ) == NULL;
519 Returns true (1) if the named field is in the blob;
521 extern int jw_exists( void* st, const char* name ) {
524 if( st != NULL && name != NULL ) {
525 rv = rmr_sym_get( st, name, OBJ_SPACE ) != NULL;
532 Returns true (1) if the primative type is value (double).
534 extern int jw_is_value( void* st, const char* name ) {
535 jthing_t* jtp; // thing that is referenced by the symtab
540 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
542 rv = jtp->prim_type == PT_VALUE;
548 Returns true (1) if the primative type is string.
550 extern int jw_is_string( void* st, const char* name ) {
551 jthing_t* jtp; // thing that is referenced by the symtab
556 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
558 rv = jtp->prim_type == PT_STRING;
565 Returns true (1) if the primative type is boolean.
567 extern int jw_is_bool( void* st, const char* name ) {
568 jthing_t* jtp; // thing that is referenced by the symtab
573 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
575 rv = jtp->prim_type == PT_BOOL;
582 Returns true (1) if the primative type was a 'null' type.
584 extern int jw_is_null( void* st, const char* name ) {
585 jthing_t* jtp; // thing that is referenced by the symtab
590 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
592 rv = jtp->prim_type == PT_NULL;
599 Look up the name in the symtab and return the string (data).
601 extern char* jw_string( void* st, const char* name ) {
602 jthing_t* jtp; // thing that is referenced by the symtab
607 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
609 if( jtp->jsmn_type == JSMN_STRING ) {
610 rv = (char *) jtp->v.pv;
618 Look up name and return the value.
620 extern double jw_value( void* st, const char* name ) {
621 jthing_t* jtp; // thing that is referenced by the symtab
626 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
628 if( jtp->jsmn_type == JSMN_PRIMITIVE ) {
637 Look up name and return the blob (symtab).
639 extern void* jw_blob( void* st, const char* name ) {
640 jthing_t* jtp; // thing that is referenced by the symtab
645 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
647 if( jtp->jsmn_type == JSMN_OBJECT ) {
648 rv = (void *) jtp->v.pv;
656 Look up the element and return boolean state; This takes the C approach and
657 returns true/false based on the value.
659 extern int jw_bool_ele( void* st, const char* name, int idx ) {
660 jthing_t* jtp; // thing that is referenced by the symtab entry
665 (jtp = suss_element( st, name, idx )) != NULL ) {
667 rv = !! ((int) jtp->v.fv);
673 Look up array element as a string. Returns NULL if:
675 name is not in the hash
676 index is out of range
677 element is not a string
679 extern char* jw_string_ele( void* st, const char* name, int idx ) {
680 jthing_t* jtp; // thing that is referenced by the symtab entry
685 (jtp = suss_element( st, name, idx )) != NULL ) {
687 if( jtp->jsmn_type == JSMN_STRING ) {
688 rv = (char *) jtp->v.pv;
696 Look up array element as a value. Returns 0 if:
698 name is not in the hash
699 index is out of range
700 element is not a value
702 extern double jw_value_ele( void* st, const char* name, int idx ) {
703 jthing_t* jtp; // thing that is referenced by the symtab entry
708 (jtp = suss_element( st, name, idx )) != NULL ) {
710 if( jtp->prim_type == PT_VALUE ) {
718 Look up the element and check to see if it is a string.
719 Return true (1) if it is.
721 extern int jw_is_string_ele( void* st, const char* name, int idx ) {
722 jthing_t* jtp; // thing that is referenced by the symtab entry
727 (jtp = suss_element( st, name, idx )) != NULL ) {
729 rv = jtp->prim_type == PT_STRING;
736 Look up the element and check to see if it is a value primative.
737 Return true (1) if it is.
739 extern int jw_is_value_ele( void* st, const char* name, int idx ) {
740 jthing_t* jtp; // thing that is referenced by the symtab entry
745 (jtp = suss_element( st, name, idx )) != NULL ) {
747 rv = jtp->prim_type == PT_VALUE;
754 Look up the element and check to see if it is a boolean primative.
755 Return true (1) if it is.
757 extern int jw_is_bool_ele( void* st, const char* name, int idx ) {
758 jthing_t* jtp; // thing that is referenced by the symtab entry
763 (jtp = suss_element( st, name, idx )) != NULL ) {
765 rv = jtp->prim_type == PT_BOOL;
772 Look up the element and check to see if it is a null primative.
773 Return true (1) if it is.
775 extern int jw_is_null_ele( void* st, const char* name, int idx ) {
776 jthing_t* jtp; // thing that is referenced by the symtab entry
781 (jtp = suss_element( st, name, idx )) != NULL ) {
783 rv = jtp->prim_type == PT_NULL;
790 Look up array element as an object. Returns NULL if:
792 name is not in the hash
793 index is out of range
794 element is not an object
796 An object in an array is a standalone symbol table. Thus the object
797 is treated differently than a nested object whose members are a
798 part of the parent namespace. An object in an array has its own
801 extern void* jw_obj_ele( void* st, const char* name, int idx ) {
802 jthing_t* jtp; // thing that is referenced by the symtab entry
807 (jtp = suss_element( st, name, idx )) != NULL ) {
809 if( jtp->jsmn_type == JSMN_OBJECT ) {
810 rv = (void *) jtp->v.pv;
818 Return the size of the array named. Returns -1 if the thing isn't an array,
819 and returns the number of elements otherwise.
821 extern int jw_array_len( void* st, const char* name ) {
822 jthing_t* jtp; // thing that is referenced by the symtab entry
827 (jtp = suss_array( st, name )) != NULL ) {