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
61 #define MGT_SPACE 2 // non-json objects in the hash (management things)
63 extern void jw_nuke( void* st );
65 // ---------------------------------------------------------------------------------------
68 This is what we will manage in the symtab. Right now we store all values (primatives)
69 as double, but we could be smarter about it and look for a decimal. Unsigned and
70 differences between long, long long etc are tough.
72 typedef struct jthing {
73 int jsmn_type; // propigated type from jsmn (jsmn constants)
74 int prim_type; // finer grained primative type (bool, null, value)
75 int nele; // number of elements if applies
84 Given the json token, 'extract' the element by marking the end with a
85 nil character, and returning a pointer to the start. We do this so that
86 we don't create a bunch of small buffers that must be found and freed; we
87 can just release the json string and we'll be done (read won't leak).
89 static char* extract( char* buf, jsmntok_t *jtoken ) {
91 return &buf[jtoken->start];
95 create a new jthing and add a reference to it in the symbol table st.
96 sets the number of elements to 1 by default.
98 static jthing_t *mk_thing( void *st, char *name, int jsmn_type ) {
103 (jtp = (jthing_t *) malloc( sizeof( *jtp ) )) != NULL ) {
106 fprintf( stderr, "<DBUG> jwrapper adding: %s type=%d\n", name, jsmn_type );
109 jtp->jsmn_type = jsmn_type;
110 jtp->prim_type = PT_UNKNOWN; // caller must set this
114 rmr_sym_put( st, name, OBJ_SPACE, jtp );
116 fprintf( stderr, "[WARN] jwrapper: unable to create '%s' type=%d\n", name, jsmn_type );
124 Find the named array. Returns a pointer to the jthing that represents
125 the array (type, size and pointer to actual array of jthings).
126 Returns nil pointer if the named thing isn't there or isn't an array.
128 static jthing_t* suss_array( void* st, const char* name ) {
129 jthing_t* jtp = NULL; // thing that is referenced by the symtab
133 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
135 jtp = jtp->jsmn_type == JSMN_ARRAY ? jtp : NULL;
142 Suss an array from the hash and return the ith element.
144 static jthing_t* suss_element( void* st, const char* name, int idx ) {
145 jthing_t* jtp; // thing that is referenced by the symtab
149 if( (jtp = suss_array( st, name )) != NULL && // have pointer
150 idx >= 0 && // and in range
153 if( (jarray = jtp->v.pv) != NULL ) {
163 Invoked for each thing in the symtab; we free the things that actually point to
164 allocated data (e.g. arrays) and recurse to handle objects.
166 static void nix_things( void* st, void* se, const char* name, void* ele, void *data ) {
171 j = (jthing_t *) ele;
173 switch( j->jsmn_type ) {
175 if( (jarray = (jthing_t *) j->v.pv) != NULL ) {
176 for( i = 0; i < j->nele; i++ ) { // must look for embedded objects
177 if( jarray[i].jsmn_type == JSMN_OBJECT ) {
178 jw_nuke( jarray[i].v.pv );
179 jarray[i].jsmn_type = JSMN_UNDEFINED; // prevent accidents
183 free( j->v.pv ); // must free the array (arrays aren't nested, so all things in the array don't reference allocated mem)
188 case JSMN_OBJECT: // delete the sub symtab
190 j->jsmn_type = JSMN_UNDEFINED; // prevent a double free
203 Nix non-json things that are also in the hash.
205 static void nix_mgt( void* st, void* se, const char* name, void* ele, void *data ) {
210 Invoked for each thing and prints what we can to stderr.
212 static void dump_things( void* st, void* se, const char* name, void* ele, void *data ) {
217 j = (jthing_t *) ele;
219 fprintf( stderr, "<DBUG> jwrapper: element '%s' has ptype %d, jsmn type %d\n", name, j->prim_type, j->jsmn_type );
221 fprintf( stderr, "<DBUG> jwrapper: element has no data: '%s'\n", name );
226 Real work for parsing an object ({...}) from the json. Called by jw_new() and
227 recurses to deal with sub-objects.
229 void* parse_jobject( void* st, char *json, char* prefix ) {
230 jthing_t *jtp; // json thing that we just created
233 char *name; // name in the json
234 char *data; // data string from the json
235 jthing_t* jarray; // array of jthings we'll coonstruct
238 int njtokens; // tokens actually sussed out
239 jsmn_parser jp; // 'parser' object
240 jsmntok_t *jtokens; // pointer to tokens returned by the parser
241 char pname[1024]; // name with prefix
242 char wbuf[256]; // temp buf to build a working name in
243 char* dstr; // dup'd string
245 jsmn_init( &jp ); // does this have a failure mode?
247 jtokens = (jsmntok_t *) malloc( sizeof( *jtokens ) * MAX_THINGS );
248 if( jtokens == NULL ) {
249 fprintf( stderr, "[CRI] jwrapper: cannot allocate tokens array\n" );
253 njtokens = jsmn_parse( &jp, json, strlen( json ), jtokens, MAX_THINGS );
255 if( jtokens[0].type != JSMN_OBJECT ) { // if it's not an object then we can't parse it.
256 fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" );
263 for( i = 1; i < njtokens-1; i++ ) {
264 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] ) );
268 for( i = 1; i < njtokens-1; i++ ) { // we'll silently skip the last token if it's "name" without a value
269 if( jtokens[i].type != JSMN_STRING ) {
270 fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n", i, jtokens[i].type, extract( json, &jtokens[i] ) );
275 name = extract( json, &jtokens[i] );
277 snprintf( pname, sizeof( pname ), "%s.%s", prefix, name );
281 size = jtokens[i].size;
283 i++; // at the data token now
284 switch( jtokens[i].type ) {
285 case JSMN_OBJECT: // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original)
286 dstr = strdup( extract( json, &jtokens[i] ) );
287 snprintf( wbuf, sizeof( wbuf ), "%s_json", name ); // must stash the json string in the symtab for clean up during nuke
288 rmr_sym_put( st, wbuf, MGT_SPACE, dstr );
290 parse_jobject( st, dstr, name ); // recurse to add the object as objectname.xxxx elements
292 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL && // create thing and reference it in current symtab
293 (jtp->v.pv = (void *) rmr_sym_alloc( 255 ) ) != NULL ) { // object is just a blob
295 dstr = strdup( extract( json, &jtokens[i] ) );
296 rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, MGT_SPACE, dstr ); // must stash json so it is freed during nuke()
297 parse_jobject( jtp->v.pv, dstr, "" ); // recurse acorss the string and build a new symtab
299 size = jtokens[i].end; // done with them, we need to skip them
301 while( i < njtokens-1 && jtokens[i].end < size ) {
303 fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
304 i, jtokens[i].start, jtokens[i].end, extract( json, &jtokens[i]) );
309 i--; // must allow loop to bump past the last
314 size = jtokens[i].size; // size is burried here, and not with the name
315 jtp = mk_thing( st, name, jtokens[i].type );
317 i++; // skip first ele; it is the whole array string which I don't grock the need for, but it's their code...
319 fprintf( stderr, "[WARN] jwrapper: memory alloc error processing element [%d] in json\n", i );
324 jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size ); // allocate the array
325 memset( jarray, 0, sizeof( *jarray ) * size );
328 for( n = 0; n < size; n++ ) { // for each array element
329 jarray[n].prim_type = PT_UNKNOWN; // assume not primative type
330 switch( jtokens[i+n].type ) {
333 jarray[n].v.pv = (void *) rmr_sym_alloc( 255 );
334 if( jarray[n].v.pv != NULL ) {
335 jarray[n].jsmn_type = JSMN_OBJECT;
336 parse_jobject( jarray[n].v.pv, extract( json, &jtokens[i+n] ), "" ); // recurse acorss the string and build a new symtab
337 osize = jtokens[i+n].end; // done with them, we need to skip them
339 while( i+n < njtokens-1 && jtokens[n+i].end < osize ) {
342 i--; // allow incr at loop end
347 fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (array) is not string or primative\n", i, n );
348 n += jtokens[i+n].size; // this should skip the nested array
355 data = extract( json, &jtokens[i+n] );
356 jarray[n].v.pv = (void *) data;
357 jarray[n].prim_type = PT_STRING;
358 jarray[n].jsmn_type = JSMN_STRING;
362 data = extract( json, &jtokens[i+n] );
367 jarray[n].prim_type = PT_BOOL;
372 jarray[n].prim_type = PT_BOOL;
376 case 'N': // assume null, nil, or some variant
378 jarray[n].prim_type = PT_NULL;
383 jarray[n].prim_type = PT_VALUE;
384 jarray[n].v.fv = strtod( data, NULL ); // store all numerics as double
388 jarray[n].jsmn_type = JSMN_PRIMITIVE;
394 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 );
402 i += size - 1; // must allow loop to push to next
406 data = extract( json, &jtokens[i] );
407 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
408 jtp->prim_type = PT_STRING;
409 jtp->v.pv = (void *) data; // just point into the large json string
414 data = extract( json, &jtokens[i] );
415 if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
416 switch( *data ) { // assume T|t is true and F|f is false
419 jtp->prim_type = PT_BOOL;
425 jtp->prim_type = PT_BOOL;
429 case 'N': // Null or some form of that
431 jtp->prim_type = PT_NULL;
436 jtp->prim_type = PT_VALUE;
437 jtp->v.fv = strtod( data, NULL ); // store all numerics as double
444 fprintf( stderr, "[WARN] jwrapper: element [%d] is undefined or of unknown type\n", i );
453 // --------------- public functions -----------------------------------------------------------------
456 Destroy all operating structures assocaited with the symtab pointer passed in.
458 extern void jw_nuke( void* st ) {
459 char* buf; // pointer to the original json to free
462 rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free any json thing that the symtab references
463 rmr_sym_foreach_class( st, MGT_SPACE, nix_mgt, NULL ); // free management things
464 rmr_sym_free( st ); // free the symtab itself
469 Scan the given st and write some useful (?) info to stderr.
471 extern void jw_dump( void* st ) {
473 rmr_sym_foreach_class( st, OBJ_SPACE, dump_things, NULL );
475 fprintf( stderr, "<DBUG> jwrapper: dump: no table\n" );
480 Given a json string, parse it, and put the things into a symtab.
481 return the symtab pointer to the caller. They pass the symtab
482 pointer back to the various get functions.
484 This is the entry point. It sets up the symbol table and invokes the parse object
485 funtion to start at the first level. Parse object will recurse for nested objects
488 extern void* jw_new( const char* json ) {
489 void *st = NULL; // symbol table
490 char* djson; // dup so we can save it
491 void* rp = NULL; // return value
493 if( json != NULL && (st = rmr_sym_alloc( MAX_THINGS/4 )) != NULL ) {
494 djson = strdup( json ); // allows user to free/overlay their buffer as needed
495 rp = parse_jobject( st, djson, "" ); // empty prefix for the root object; parse_jo will clean up and free st
499 rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, MGT_SPACE, djson ); // must have a reference to the string until symtab is trashed
507 Returns true (1) if the named field is missing.
509 extern int jw_missing( void* st, const char* name ) {
512 if( st != NULL && name != NULL ) {
513 rv = rmr_sym_get( st, name, OBJ_SPACE ) == NULL;
520 Returns true (1) if the named field is in the blob;
522 extern int jw_exists( void* st, const char* name ) {
525 if( st != NULL && name != NULL ) {
526 rv = rmr_sym_get( st, name, OBJ_SPACE ) != NULL;
533 Returns true (1) if the primative type is value (double).
535 extern int jw_is_value( void* st, const char* name ) {
536 jthing_t* jtp; // thing that is referenced by the symtab
541 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
543 rv = jtp->prim_type == PT_VALUE;
549 Returns true (1) if the primative type is string.
551 extern int jw_is_string( void* st, const char* name ) {
552 jthing_t* jtp; // thing that is referenced by the symtab
557 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
559 rv = jtp->prim_type == PT_STRING;
566 Returns true (1) if the primative type is boolean.
568 extern int jw_is_bool( void* st, const char* name ) {
569 jthing_t* jtp; // thing that is referenced by the symtab
574 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
576 rv = jtp->prim_type == PT_BOOL;
583 Returns true (1) if the primative type was a 'null' type.
585 extern int jw_is_null( void* st, const char* name ) {
586 jthing_t* jtp; // thing that is referenced by the symtab
591 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
593 rv = jtp->prim_type == PT_NULL;
600 Look up the name in the symtab and return the string (data).
602 extern char* jw_string( void* st, const char* name ) {
603 jthing_t* jtp; // thing that is referenced by the symtab
608 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
610 if( jtp->jsmn_type == JSMN_STRING ) {
611 rv = (char *) jtp->v.pv;
619 Look up name and return the value.
621 extern double jw_value( void* st, const char* name ) {
622 jthing_t* jtp; // thing that is referenced by the symtab
627 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
629 if( jtp->jsmn_type == JSMN_PRIMITIVE ) {
638 Look up name and return the blob (symtab).
640 extern void* jw_blob( void* st, const char* name ) {
641 jthing_t* jtp; // thing that is referenced by the symtab
646 (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
648 if( jtp->jsmn_type == JSMN_OBJECT ) {
649 rv = (void *) jtp->v.pv;
657 Look up the element and return boolean state; This takes the C approach and
658 returns true/false based on the value.
660 extern int jw_bool_ele( void* st, const char* name, int idx ) {
661 jthing_t* jtp; // thing that is referenced by the symtab entry
666 (jtp = suss_element( st, name, idx )) != NULL ) {
668 rv = !! ((int) jtp->v.fv);
674 Look up array element as a string. Returns NULL if:
676 name is not in the hash
677 index is out of range
678 element is not a string
680 extern char* jw_string_ele( void* st, const char* name, int idx ) {
681 jthing_t* jtp; // thing that is referenced by the symtab entry
686 (jtp = suss_element( st, name, idx )) != NULL ) {
688 if( jtp->jsmn_type == JSMN_STRING ) {
689 rv = (char *) jtp->v.pv;
697 Look up array element as a value. Returns 0 if:
699 name is not in the hash
700 index is out of range
701 element is not a value
703 extern double jw_value_ele( void* st, const char* name, int idx ) {
704 jthing_t* jtp; // thing that is referenced by the symtab entry
709 (jtp = suss_element( st, name, idx )) != NULL ) {
711 if( jtp->prim_type == PT_VALUE ) {
719 Look up the element and check to see if it is a string.
720 Return true (1) if it is.
722 extern int jw_is_string_ele( void* st, const char* name, int idx ) {
723 jthing_t* jtp; // thing that is referenced by the symtab entry
728 (jtp = suss_element( st, name, idx )) != NULL ) {
730 rv = jtp->prim_type == PT_STRING;
737 Look up the element and check to see if it is a value primative.
738 Return true (1) if it is.
740 extern int jw_is_value_ele( void* st, const char* name, int idx ) {
741 jthing_t* jtp; // thing that is referenced by the symtab entry
746 (jtp = suss_element( st, name, idx )) != NULL ) {
748 rv = jtp->prim_type == PT_VALUE;
755 Look up the element and check to see if it is a boolean primative.
756 Return true (1) if it is.
758 extern int jw_is_bool_ele( void* st, const char* name, int idx ) {
759 jthing_t* jtp; // thing that is referenced by the symtab entry
764 (jtp = suss_element( st, name, idx )) != NULL ) {
766 rv = jtp->prim_type == PT_BOOL;
773 Look up the element and check to see if it is a null primative.
774 Return true (1) if it is.
776 extern int jw_is_null_ele( void* st, const char* name, int idx ) {
777 jthing_t* jtp; // thing that is referenced by the symtab entry
782 (jtp = suss_element( st, name, idx )) != NULL ) {
784 rv = jtp->prim_type == PT_NULL;
791 Look up array element as an object. Returns NULL if:
793 name is not in the hash
794 index is out of range
795 element is not an object
797 An object in an array is a standalone symbol table. Thus the object
798 is treated differently than a nested object whose members are a
799 part of the parent namespace. An object in an array has its own
802 extern void* jw_obj_ele( void* st, const char* name, int idx ) {
803 jthing_t* jtp; // thing that is referenced by the symtab entry
808 (jtp = suss_element( st, name, idx )) != NULL ) {
810 if( jtp->jsmn_type == JSMN_OBJECT ) {
811 rv = (void *) jtp->v.pv;
819 Return the size of the array named. Returns -1 if the thing isn't an array,
820 and returns the number of elements otherwise.
822 extern int jw_array_len( void* st, const char* name ) {
823 jthing_t* jtp; // thing that is referenced by the symtab entry
828 (jtp = suss_array( st, name )) != NULL ) {