4 ==================================================================================
5 Copyright (c) 2020 Nokia
6 Copyright (c) 2020 AT&T Intellectual Property.
8 Licensed under the Apache License, Version 2.0 (the "License");
9 you may not use this file except in compliance with the License.
10 You may obtain a copy of the License at
12 http://www.apache.org/licenses/LICENSE-2.0
14 Unless required by applicable law or agreed to in writing, software
15 distributed under the License is distributed on an "AS IS" BASIS,
16 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 See the License for the specific language governing permissions and
18 limitations under the License.
19 ==================================================================================
24 Abstract: A wrapper interface to the jsmn library which makes it a bit easier
25 to use. Parses a json string capturing the contents in a symtab.
27 This code is based on the AT&T VFd open source library available
28 on github.com/att/vfd. The changes are mostly to port to the
29 RMR version of symtab from VFd's version.
31 Author: E. Scott Daniels
45 #define JSMN_STATIC 1 // jsmn no longer builds into a library; this pulls as static functions
47 //#include <../../ext/jsmn/jsmn.h>
49 #include <rmr/rmr_symtab.h>
51 #define JSON_SYM_NAME "_jw_json_string"
52 #define MAX_THINGS 1024 * 4 // max objects/elements
54 #define PT_UNKNOWN 0 // primative types; unknown for non prim
60 #define OBJ_SPACE 1 // space in the symbol table where json bits are stashed
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)
188 j->jsmn_type = JSMN_UNDEFINED; // prevent a double free
195 Invoked for each thing and prints what we can to stderr.
197 static void dump_things( void* st, void* se, const char* name, void* ele, void *data ) {
202 j = (jthing_t *) ele;
204 fprintf( stderr, "<DBUG> jwrapper: element '%s' has ptype %d, jsmn type %d\n", name, j->prim_type, j->jsmn_type );
206 fprintf( stderr, "<DBUG> jwrapper: element has no data: '%s'\n", name );
211 Real work for parsing an object ({...}) from the json. Called by jw_new() and
212 recurses to deal with sub-objects.
214 void* parse_jobject( void* st, char *json, char* prefix ) {
215 jthing_t *jtp; // json thing that we just created
218 char *name; // name in the json
219 char *data; // data string from the json
220 jthing_t* jarray; // array of jthings we'll coonstruct
223 int njtokens; // tokens actually sussed out
224 jsmn_parser jp; // 'parser' object
225 jsmntok_t *jtokens; // pointer to tokens returned by the parser
226 char pname[1024]; // name with prefix
227 char wbuf[256]; // temp buf to build a working name in
228 char* dstr; // dup'd string
230 jsmn_init( &jp ); // does this have a failure mode?
232 jtokens = (jsmntok_t *) malloc( sizeof( *jtokens ) * MAX_THINGS );
233 if( jtokens == NULL ) {
234 fprintf( stderr, "[CRI] jwrapper: cannot allocate tokens array\n" );
238 njtokens = jsmn_parse( &jp, json, strlen( json ), jtokens, MAX_THINGS );
240 if( jtokens[0].type != JSMN_OBJECT ) { // if it's not an object then we can't parse it.
241 fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" );
246 for( i = 1; i < njtokens-1; i++ ) {
247 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] ) );
251 for( i = 1; i < njtokens-1; i++ ) { // we'll silently skip the last token if it's "name" without a value
252 if( jtokens[i].type != JSMN_STRING ) {
253 fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n", i, jtokens[i].type, extract( json, &jtokens[i] ) );
257 name = extract( json, &jtokens[i] );
259 snprintf( pname, sizeof( pname ), "%s.%s", prefix, name );
263 size = jtokens[i].size;
265 i++; // at the data token now
266 switch( jtokens[i].type ) {
267 case JSMN_OBJECT: // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original)
268 dstr = strdup( extract( json, &jtokens[i] ) );
269 snprintf( wbuf, sizeof( wbuf ), "%s_json", name ); // must stash the json string in the symtab for clean up during nuke
270 rmr_sym_put( st, wbuf, OBJ_SPACE, dstr );
272 parse_jobject( st, dstr, name ); // recurse to add the object as objectname.xxxx elements
274 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL && // create thing and reference it in current symtab
275 (jtp->v.pv = (void *) rmr_sym_alloc( 255 ) ) != NULL ) { // object is just a blob
277 dstr = strdup( extract( json, &jtokens[i] ) );
278 rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, OBJ_SPACE, dstr ); // must stash json so it is freed during nuke()
279 parse_jobject( jtp->v.pv, dstr, "" ); // recurse acorss the string and build a new symtab
281 size = jtokens[i].end; // done with them, we need to skip them
283 while( i < njtokens-1 && jtokens[i].end < size ) {
285 fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
286 i, jtokens[i].start, jtokens[i].end, extract( json, &jtokens[i]) );
291 i--; // must allow loop to bump past the last
296 size = jtokens[i].size; // size is burried here, and not with the name
297 jtp = mk_thing( st, name, jtokens[i].type );
299 i++; // skip first ele; it is the whole array string which I don't grock the need for, but it's their code...
301 fprintf( stderr, "[WARN] jwrapper: memory alloc error processing element [%d] in json\n", i );
305 jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size ); // allocate the array
306 memset( jarray, 0, sizeof( *jarray ) * size );
309 for( n = 0; n < size; n++ ) { // for each array element
310 jarray[n].prim_type = PT_UNKNOWN; // assume not primative type
311 switch( jtokens[i+n].type ) {
314 jarray[n].v.pv = (void *) rmr_sym_alloc( 255 );
315 if( jarray[n].v.pv != NULL ) {
316 jarray[n].jsmn_type = JSMN_OBJECT;
317 parse_jobject( jarray[n].v.pv, extract( json, &jtokens[i+n] ), "" ); // recurse acorss the string and build a new symtab
318 osize = jtokens[i+n].end; // done with them, we need to skip them
320 while( i+n < njtokens-1 && jtokens[n+i].end < osize ) {
323 i--; // allow incr at loop end
328 fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (array) is not string or primative\n", i, n );
329 n += jtokens[i+n].size; // this should skip the nested array
333 data = extract( json, &jtokens[i+n] );
334 jarray[n].v.pv = (void *) data;
335 jarray[n].prim_type = PT_STRING;
336 jarray[n].jsmn_type = JSMN_STRING;
340 data = extract( json, &jtokens[i+n] );
345 jarray[n].prim_type = PT_BOOL;
350 jarray[n].prim_type = PT_BOOL;
354 case 'N': // assume null, nil, or some variant
356 jarray[n].prim_type = PT_NULL;
361 jarray[n].prim_type = PT_VALUE;
362 jarray[n].v.fv = strtod( data, NULL ); // store all numerics as double
366 jarray[n].jsmn_type = JSMN_PRIMITIVE;
372 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 );
379 i += size - 1; // must allow loop to push to next
383 data = extract( json, &jtokens[i] );
384 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
385 jtp->prim_type = PT_STRING;
386 jtp->v.pv = (void *) data; // just point into the large json string
391 data = extract( json, &jtokens[i] );
392 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
393 switch( *data ) { // assume T|t is true and F|f is false
396 jtp->prim_type = PT_BOOL;
402 jtp->prim_type = PT_BOOL;
406 case 'N': // Null or some form of that
408 jtp->prim_type = PT_NULL;
413 jtp->prim_type = PT_VALUE;
414 jtp->v.fv = strtod( data, NULL ); // store all numerics as double
421 fprintf( stderr, "[WARN] jwrapper: element [%d] is undefined or of unknown type\n", i );
430 // --------------- public functions -----------------------------------------------------------------
433 Destroy all operating structures assocaited with the symtab pointer passed in.
435 extern void jw_nuke( void* st ) {
436 char* buf; // pointer to the original json to free
439 rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free anything that the symtab references
440 rmr_sym_free( st ); // free the symtab itself
445 Scan the given st and write some useful (?) info to stderr.
447 extern void jw_dump( void* st ) {
449 rmr_sym_foreach_class( st, OBJ_SPACE, dump_things, NULL );
451 fprintf( stderr, "<DBUG> jwrapper: dump: no table\n" );
456 Given a json string, parse it, and put the things into a symtab.
457 return the symtab pointer to the caller. They pass the symtab
458 pointer back to the various get functions.
460 This is the entry point. It sets up the symbol table and invokes the parse object
461 funtion to start at the first level. Parse object will recurse for nested objects
464 extern void* jw_new( const char* json ) {
465 void *st; // symbol table
466 char* djson; // dup so we can save it
467 void* rp = NULL; // return value
469 if( json != NULL && (st = rmr_sym_alloc( MAX_THINGS )) != NULL ) {
470 djson = strdup( json ); // allows user to free/overlay their buffer as needed
471 rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, OBJ_SPACE, djson ); // must have a reference to the string until symtab is trashed
473 rp = parse_jobject( st, djson, "" ); // empty prefix for the root object
480 Returns true (1) if the named field is missing.
482 extern int jw_missing( void* st, const char* name ) {
485 if( st != NULL && name != NULL ) {
486 rv = rmr_sym_get( st, name, OBJ_SPACE ) == NULL;
493 Returns true (1) if the named field is in the blob;
495 extern int jw_exists( void* st, const char* name ) {
498 if( st != NULL && name != NULL ) {
499 rv = rmr_sym_get( st, name, OBJ_SPACE ) != NULL;
506 Returns true (1) if the primative type is value (double).
508 extern int jw_is_value( void* st, const char* name ) {
509 jthing_t* jtp; // thing that is referenced by the symtab
514 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
516 rv = jtp->prim_type == PT_VALUE;
522 Returns true (1) if the primative type is string.
524 extern int jw_is_string( void* st, const char* name ) {
525 jthing_t* jtp; // thing that is referenced by the symtab
530 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
532 rv = jtp->prim_type == PT_STRING;
539 Returns true (1) if the primative type is boolean.
541 extern int jw_is_bool( void* st, const char* name ) {
542 jthing_t* jtp; // thing that is referenced by the symtab
547 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
549 rv = jtp->prim_type == PT_BOOL;
556 Returns true (1) if the primative type was a 'null' type.
558 extern int jw_is_null( void* st, const char* name ) {
559 jthing_t* jtp; // thing that is referenced by the symtab
564 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
566 rv = jtp->prim_type == PT_NULL;
573 Look up the name in the symtab and return the string (data).
575 extern char* jw_string( void* st, const char* name ) {
576 jthing_t* jtp; // thing that is referenced by the symtab
581 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
583 if( jtp->jsmn_type == JSMN_STRING ) {
584 rv = (char *) jtp->v.pv;
592 Look up name and return the value.
594 extern double jw_value( void* st, const char* name ) {
595 jthing_t* jtp; // thing that is referenced by the symtab
600 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
602 if( jtp->jsmn_type == JSMN_PRIMITIVE ) {
611 Look up name and return the blob (symtab).
613 extern void* jw_blob( void* st, const char* name ) {
614 jthing_t* jtp; // thing that is referenced by the symtab
619 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
621 if( jtp->jsmn_type == JSMN_OBJECT ) {
622 rv = (void *) jtp->v.pv;
630 Look up the element and return boolean state; This takes the C approach and
631 returns true/false based on the value.
633 extern int jw_bool_ele( void* st, const char* name, int idx ) {
634 jthing_t* jtp; // thing that is referenced by the symtab entry
639 (jtp = suss_element( st, name, idx )) != NULL ) {
641 rv = !! ((int) jtp->v.fv);
647 Look up array element as a string. Returns NULL if:
649 name is not in the hash
650 index is out of range
651 element is not a string
653 extern char* jw_string_ele( void* st, const char* name, int idx ) {
654 jthing_t* jtp; // thing that is referenced by the symtab entry
659 (jtp = suss_element( st, name, idx )) != NULL ) {
661 if( jtp->jsmn_type == JSMN_STRING ) {
662 rv = (char *) jtp->v.pv;
670 Look up array element as a value. Returns 0 if:
672 name is not in the hash
673 index is out of range
674 element is not a value
676 extern double jw_value_ele( void* st, const char* name, int idx ) {
677 jthing_t* jtp; // thing that is referenced by the symtab entry
682 (jtp = suss_element( st, name, idx )) != NULL ) {
684 if( jtp->prim_type == PT_VALUE ) {
692 Look up the element and check to see if it is a string.
693 Return true (1) if it is.
695 extern int jw_is_string_ele( void* st, const char* name, int idx ) {
696 jthing_t* jtp; // thing that is referenced by the symtab entry
701 (jtp = suss_element( st, name, idx )) != NULL ) {
703 rv = jtp->prim_type == PT_STRING;
710 Look up the element and check to see if it is a value primative.
711 Return true (1) if it is.
713 extern int jw_is_value_ele( void* st, const char* name, int idx ) {
714 jthing_t* jtp; // thing that is referenced by the symtab entry
719 (jtp = suss_element( st, name, idx )) != NULL ) {
721 rv = jtp->prim_type == PT_VALUE;
728 Look up the element and check to see if it is a boolean primative.
729 Return true (1) if it is.
731 extern int jw_is_bool_ele( void* st, const char* name, int idx ) {
732 jthing_t* jtp; // thing that is referenced by the symtab entry
737 (jtp = suss_element( st, name, idx )) != NULL ) {
739 rv = jtp->prim_type == PT_BOOL;
746 Look up the element and check to see if it is a null primative.
747 Return true (1) if it is.
749 extern int jw_is_null_ele( void* st, const char* name, int idx ) {
750 jthing_t* jtp; // thing that is referenced by the symtab entry
755 (jtp = suss_element( st, name, idx )) != NULL ) {
757 rv = jtp->prim_type == PT_NULL;
764 Look up array element as an object. Returns NULL if:
766 name is not in the hash
767 index is out of range
768 element is not an object
770 An object in an array is a standalone symbol table. Thus the object
771 is treated differently than a nested object whose members are a
772 part of the parent namespace. An object in an array has its own
775 extern void* jw_obj_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 if( jtp->jsmn_type == JSMN_OBJECT ) {
784 rv = (void *) jtp->v.pv;
792 Return the size of the array named. Returns -1 if the thing isn't an array,
793 and returns the number of elements otherwise.
795 extern int jw_array_len( void* st, const char* name ) {
796 jthing_t* jtp; // thing that is referenced by the symtab entry
801 (jtp = suss_array( st, name )) != NULL ) {