1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
|
#ifndef SG_JSON_H
#define SG_JSON_H
/*
* Copyright (c) 2023 Douglas Gilbert.
* All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the BSD_LICENSE file.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <inttypes.h>
#include <stdio.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* JSON support functions and structures follow. The prefix "sgj_" is used
* for sg3_utils JSON functions, types and values. */
/* Following macro for sgj_pr_hr() which takes printf() like arguments */
#if __USE_MINGW_ANSI_STDIO -0 == 1
#define __printf(a, b) __attribute__((__format__(gnu_printf, a, b)))
#elif defined(__GNUC__) || defined(__clang__)
#define __printf(a, b) __attribute__((__format__(printf, a, b)))
#else
#define __printf(a, b)
#endif
enum sgj_separator_t {
SGJ_SEP_NONE = 0,
SGJ_SEP_SPACE_1,
SGJ_SEP_SPACE_2,
SGJ_SEP_SPACE_3,
SGJ_SEP_SPACE_4,
SGJ_SEP_EQUAL_NO_SPACE,
SGJ_SEP_EQUAL_1_SPACE,
SGJ_SEP_SPACE_EQUAL_SPACE,
SGJ_SEP_COLON_NO_SPACE,
SGJ_SEP_COLON_1_SPACE,
};
typedef void * sgj_opaque_p;
/* Apart from the state information at the end of this structure, the earlier
* fields are initialized from the command line argument given to the
* --json= option. If there is no argument then they initialized as shown. */
typedef struct sgj_state_t {
/* the following set by default, the SG3_UTILS_JSON_OPTS environment
* variable or command line argument to --json option, in that order. */
bool pr_as_json; /* = false (def: is plain text output) */
bool pr_exit_status; /* 'e' (def: true) */
bool pr_hex; /* 'h' (def: false) */
bool pr_leadin; /* 'l' (def: true) */
bool pr_name_ex; /* 'n' name_extra (information) (def: false) */
bool pr_out_hr; /* 'o' (def: false) */
bool pr_packed; /* 'k' (def: false) only when !pr_pretty */
bool pr_pretty; /* 'p' (def: true) */
bool pr_string; /* 's' (def: true) */
char pr_format; /* (def: '\0') */
int pr_indent_size; /* digit (def: 4) */
int verbose; /* 'v' (def: 0) incremented each appearance */
int q_counter; /* 'q' (def: 0) extra, for using apps */
int z_counter; /* 'z' (def: 0) extra, for using apps */
/* the following hold state information */
int first_bad_char; /* = '\0' */
sgj_opaque_p basep; /* base JSON object pointer */
sgj_opaque_p out_hrp; /* JSON array pointer when pr_out_hr set. Each
* element contains a line of plain text. The
* array's JSON name is 'plain_text_output' */
sgj_opaque_p userp; /* for temporary usage */
} sgj_state;
/* This function tries to convert the in_name C string to the "snake_case"
* convention so the output sn_name only contains lower case ASCII letters,
* numerals and "_" as a separator. Any leading or trailing underscores
* are removed as are repeated underscores (e.g. "_Snake __ case_" becomes
* "snake_case"). Parentheses and the characters between them are removed.
* Returns sn_name (i.e. the pointer to the output buffer).
* Note: strlen(in_name) should be <= max_sn_name_len . */
char * sgj_convert2snake_rm_parens(const char * in_name, char * sn_name,
int max_sn_name_len);
/* Returns sn_name which only contains lower case ASCII letters, numerals
* and "_" as a separator, taken from in_name. All other characters are
* converted to "_". Any leading or trailing underscores are removed as
* are repeated underscores (e.g. "_Output power (mW)!" becomes
* "output_power_mw". */
char * sgj_convert2snake(const char * in, char * out, int maxlen_out);
/* Is in_name made up of only lower case alphanumerics and underscores? */
bool sgj_is_snake_name(const char * in_name);
/* There are many variants of JSON supporting functions below and some
* abbreviations are used to shorten their function names:
* sgj_ - prefix of all the functions related to (non-)JSON output
* hr - human readable form (same meaning as "plain text")
* js - JSON only output
* haj - human readable and JSON output, if JSON output is selected
* then the normal output goes in 'plain_text_output' array
* pr - has printf() like variadic arguments
* _r - suffix indicating the return value should/must be used
* nv - adds a name-value JSON field (or several)
* o - value is the provided JSON object (or array)
* i - value is a JSON integer object (int64_t or uint64_t)
* b - value is a JSON boolean object
* s - value is a JSON string object
* str - same as s
* hex - value is hexadecimal in a JSON string object
* _nex - extra 'name_extra' JSON string object about name
* new - object that needs sgj_free_unattached() if not attached
*
* */
/* If jsp in non-NULL and jsp->pr_as_json is true then this call is ignored
* unless jsp->pr_out_hrp is true. Otherwise this function prints to stdout
* like printf(fmt, ...); note that no LF is added. In the jsp->pr_out_hrp is
* true case, nothing is printed to stdout but instead is placed into a JSON
* array (jsp->out_hrp) after some preprocessing. That preprocessing involves
* removing a leading LF from 'fmt' (if present) and up to two trailing LF
* characters. */
void sgj_pr_hr(sgj_state * jsp, const char * fmt, ...) __printf(2, 3);
/* Initializes the state object pointed to by jsp based on the argument
* given to the right of --json= pointed to by j_optarg. If it is NULL
* then state object gets its default values. Returns true if argument
* to --json= is decoded properly, else returns false and places the
* first "bad" character in jsp->first_bad_char . Note that no JSON
* in-core tree needs to exist when this function is called. */
bool sgj_init_state(sgj_state * jsp, const char * j_optarg);
/* sgj_start() creates a JSON in-core tree and returns a pointer to it (or
* NULL if the associated heap allocation fails). It should be paired with
* sgj_finish() to clean up (i.e. remove all heap allocations) all the
* elements (i.e. JSON objects and arrays) that have been placed in that
* in-core tree. If jsp is NULL nothing further happens. Otherwise the pointer
* to be returned is placed in jsp->basep. If jsp->pr_leadin is true and
* util_name is non-NULL then a "utility_invoked" JSON object is made with
* "name", and "version_date" object fields. If the jsp->pr_out_hr field is
* true a named array called "plain_text_output" is added to the
* "utility_invoked" object (creating it in the case when jsp->pr_leadin is
* false) and a pointer to that array object is placed in jsp->objectp . The
* returned pointer is not usually needed but if it is NULL then a heap
* allocation has failed. */
sgj_opaque_p sgj_start_r(const char * util_name, const char * ver_str,
int argc, char *argv[], sgj_state * jsp);
/* These are low level functions returning a pointer to a newly created JSON
* object or array. If jsp is NULL or jsp->pr_as_json is false nothing happens
* and NULL is returned. Note that this JSON object is _not_ placed in the
* in-core tree controlled by jsp (jsp->basep); it may be added later as the
* fourth argument to sgj_js_nv_o(), for example. */
sgj_opaque_p sgj_new_unattached_object_r(sgj_state * jsp);
sgj_opaque_p sgj_new_unattached_array_r(sgj_state * jsp);
/* following are similar, useful for leaves */
sgj_opaque_p sgj_new_unattached_string_r(sgj_state * jsp, const char * value);
sgj_opaque_p sgj_new_unattached_str_len_r(sgj_state * jsp, const char * value,
int vlen);
sgj_opaque_p sgj_new_unattached_integer_r(sgj_state * jsp, uint64_t value);
sgj_opaque_p sgj_new_unattached_bool_r(sgj_state * jsp, bool value);
sgj_opaque_p sgj_new_unattached_null_r(sgj_state * jsp);
/* If jsp is NULL or jsp->pr_as_json is false nothing happens and NULL is
* returned. Otherwise it creates a new named object (whose name is what
* 'sn_name' points to) at 'jop' with an empty object as its value; a pointer
* to that empty object is returned. If 'jop' is NULL then jsp->basep is
* used instead. The returned value should always be checked (for NULL)
* and if not, used. */
sgj_opaque_p sgj_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name);
sgj_opaque_p sgj_snake_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop,
const char * conv2sname);
/* If jsp is NULL or jsp->pr_as_json is false nothing happens and NULL is
* returned. Otherwise it creates a new named object (whose name is what
* 'sn_name' points to) at 'jop' with an empty array as its value; a pointer
* to that empty array is returned. If 'jop' is NULL then jsp->basep is
* used instead. The returned value should always * be checked (for NULL)
* and if not, used. */
sgj_opaque_p sgj_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name);
sgj_opaque_p sgj_snake_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop,
const char * conv2sname);
/* If either jsp or value is NULL or jsp->pr_as_json is false then nothing
* happens and NULL is returned. The insertion point is at jop but if it is
* NULL jsp->basep is used. If 'sn_name' is non-NULL a new named JSON object
* is added using 'sn_name' and the associated value is a JSON string formed
* from 'value'. If 'name' is NULL then 'jop' is assumed to be a JSON array
* and a JSON string formed from 'value' is added. Note that the
* jsp->pr_string setting is ignored by this function. If successful returns
* a pointer to the newly formed JSON string. */
sgj_opaque_p sgj_js_nv_s(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, const char * value);
sgj_opaque_p sgj_js_nv_s_len(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name,
const char * value, int vlen);
/* This variant checks 'value' for characters that are not permitted in JSON
* strings and takes appropriate actions. See sgj_conv2json_string() below. */
sgj_opaque_p sgj_js_nv_s_len_chk(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name,
const uint8_t * value, int vlen);
/* If either jsp is NULL or jsp->pr_as_json is false then nothing happens and
* NULL is returned. The insertion point is at jop but if it is NULL
* jsp->basep is used. If 'sn_name' is non-NULL a new named JSON object is
* added using 'sn_name' and the associated value is a JSON integer formed
* from 'value'. If 'sn_name' is NULL then 'jop' is assumed to be a JSON array
* and a JSON integer formed from 'value' is added. If successful returns a
* a pointer newly formed JSON integer. */
sgj_opaque_p sgj_js_nv_i(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, int64_t value);
/* If either jsp is NULL or jsp->pr_as_json is false then nothing happens and
* NULL is returned. The insertion point is at jop but if it is NULL
* jsp->basep is used. If 'sn_name' is non-NULL a new named JSON object is
* added using 'sn_name' and the associated value is a JSON boolean formed
* from 'value'. If 'name' is NULL then 'jop' is assumed to be a JSON array
* and a JSON boolean formed from 'value' is added. If successful returns a
* a pointer newly formed JSON boolean. */
sgj_opaque_p sgj_js_nv_b(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, bool value);
/* If jsp is NULL, jsp->pr_as_json is false or ua_jop is NULL nothing then
* happens and NULL is returned. 'jop' is the insertion point but if it is
* NULL jsp->basep is used instead. If 'sn_name' is non-NULL a new named JSON
* object is added using 'sn_name' and the associated value is ua_jop. If
* 'sn_name' is NULL then 'jop' is assumed to be a JSON array and ua_jop is
* added to it. If successful returns ua_jop . The "ua_" prefix stands for
* unattached. That should be the case before invocation and it will be
* attached to jop after a successful invocation. This means that ua_jop
* must have been created by sgj_new_unattached_object_r() or similar. */
sgj_opaque_p sgj_js_nv_o(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, sgj_opaque_p ua_jop);
/* This function only produces JSON output if jsp is non-NULL and
* jsp->pr_as_json is true. It adds a named object at 'jop' (or jop->basep
* if jop is NULL) along with a value. If jsp->pr_hex is true then that
* value is two sub-objects, one named 'i' with a 'value' as a JSON integer,
* the other one named 'hex' with 'value' rendered as hex in a JSON string.
* If jsp->pr_hex is false then there are no sub-objects and the 'value' is
* rendered as JSON integer. */
void sgj_js_nv_ihex(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, uint64_t value);
/* This function only produces JSON output if jsp is non-NULL and
* jsp->pr_as_json is true. It adds a named object at 'jop' (or jop->basep
* if jop is NULL) along with a value. If jsp->pr_string is true then that
* value is two sub-objects, one named 'i' with a 'val_i' as a JSON integer,
* the other one named str_name with val_s rendered as a JSON string. If
* str_name is NULL then "meaning" will be used. If jsp->pr_string is false
* then there are no sub-objects and the 'val_i' is rendered as a JSON
* integer. */
void sgj_js_nv_istr(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, int64_t val_i,
const char * str_name, const char * val_s);
/* Similar to sgj_js_nv_istr(). The hex output is conditional jsp->pr_hex . */
void sgj_js_nv_ihexstr(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, int64_t val_i,
const char * str_name, const char * val_s);
/* This function only produces JSON output if jsp && jsp->pr_as_json is true.
* It adds a named object at 'jop' (or jop->basep if jop is NULL) along with
* a value. If jsp->pr_name_ex is true then that value has two sub-objects,
* one named 'i' with a 'val_i' as a JSON integer, the other one named
* "name_extra" with value nex_s rendered as a JSON string. If jsp->pr_hex
* and 'hex_as_well' are true, then a sub-object named 'hex' with a value
* rendered as a hex string equal to val_i. If jsp->pr_name_ex is false and
* either jsp->pr_hex or hex_as_well are false then there are no sub-objects
* and the 'val_i' is rendered as a JSON integer. */
void sgj_js_nv_ihex_nex(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, int64_t val_i, bool hex_as_well,
const char * nex_s);
void sgj_js_nv_ihexstr_nex(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, int64_t val_i,
bool hex_as_well, const char * str_name,
const char * val_s, const char * nex_s);
/* String value version of above *_nex() functions. If both 'val_s' and
* 'nex_s' are non-NULL forms sub-object named 'sn_name' with the names
* "s" and "name_extra" and values of the last two arguments. If one of
* the last two arguments is NULL similar to sgj_js_nv_s() using the non-NULL
* argument as the value. */
void sgj_js_nv_s_nex(sgj_state * jsp, sgj_opaque_p jop, const char * sn_name,
const char * val_s, const char * nex_s);
/* Add named field whose value is a (large) JSON string made up of num_bytes
* ASCII hexadecimal bytes (each two hex digits separated by a space) starting
* at byte_arr. The heap is used for intermediate storage so num_bytes can
* be arbitrarily large. */
void sgj_js_nv_hex_bytes(sgj_state * jsp, sgj_opaque_p jop,
const char * sn_name, const uint8_t * byte_arr,
int num_bytes);
/* The '_haj_' refers to generating output both for human readable and/or
* JSON with a single invocation. If jsp is non-NULL and jsp->pr_out_hr is
* true then both JSON and plain text output is formed (and the latter is
* placed in the jsp->out_hrp JSON array). The plain text form will have
* leadin_sp spaces followed by 'name' then a separator, then 'value' with a
* trailing LF. If 'name' is NULL then it and the separator are ignored. If
* there is JSON output, then leadin_sp and sep are ignored. If 'jop' is NULL
* then basep->basep is used. If 'name' is NULL then a JSON string object,
* made from 'value' is added to the JSON array pointed to by 'jop'.
* Otherwise a 'name'-d JSON object whose value is a JSON string object made
* from 'value' is added at 'jop'. */
void sgj_haj_vs(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep,
const char * value);
/* Similar to sgj_haj_vs()'s description with 'JSON string object'
* replaced by 'JSON integer object'. hex_haj when set will cause the value
* to be output in <0x%x> form (default is a signed decimal 64 bit integer)
* in the plain text rendering. For JSON output hex_haj has the same
* effect as hex_as_well. */
void sgj_haj_vi(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep,
int64_t value, bool hex_haj);
void sgj_haj_vistr(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep,
int64_t value, bool hex_haj, const char * val_s);
/* The '_nex' refers to a "name_extra" (information) sub-object (a JSON
* string) which explains a bit more about the 'name' entry. This is useful
* when T10 specifies the name as an abbreviation (e.g. SYSV). Whether this
* sub-object is shown in the JSON output is controlled by the 'n' control
* character. */
void sgj_haj_vi_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep,
int64_t value, bool hex_haj, const char * nex_s);
void sgj_haj_vistr_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep,
int64_t value, bool hex_haj,
const char * val_s, const char * nex_s);
/* Similar to above '_haj_' calls but a named sub-object is always formed
* containing a JSON integer object named "i" whose value is 'value'. The
* returned pointer is to that sub-object. */
sgj_opaque_p sgj_haj_subo_r(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep,
int64_t value, bool hex_haj);
/* Similar to sgj_haj_vs()'s description with 'JSON string object' replaced
* by 'JSON boolean object'. */
void sgj_haj_vb(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp,
const char * name, enum sgj_separator_t sep, bool value);
/* Breaks up the string pointed to by 'sp' into lines and adds them to the
* jsp->out_hrp array. Treat '\n' in sp as line breaks. Consumes characters
* from sp until either a '\0' is found or slen is exhausted. Add each line
* to jsp->out_hrp JSON array (if conditions met). Outputs to stdout. */
void sgj_hr_str_out(sgj_state * jsp, const char * sp, int slen);
/* Nothing in the in-core JSON tree is actually printed to 'fp' (typically
* stdout) until this call is made. If jsp is NULL, jsp->pr_as_json is false
* or jsp->basep is NULL then this function does nothing. If jsp->exit_status
* is true then a new JSON object named "exit_status" and the 'exit_status'
* value rendered as a JSON integer is appended to jsp->basep. The in-core
* JSON tree with jsp->basep as its root is streamed to 'fp'. */
void sgj_js2file_estr(sgj_state * jsp, sgj_opaque_p jop, int exit_status,
const char * estr, FILE * fp);
/* This function is only needed if the pointer returned from either
* sgj_new_unattached_object_r() or sgj_new_unattached_array_r() has not
* been attached into the in-core JSON tree whose root is jsp->basep . */
void sgj_free_unattached(sgj_opaque_p jop);
/* If jsp is NULL or jsp->basep is NULL then this function does nothing.
* This function does bottom up, heap freeing of all the in-core JSON
* objects and arrays attached to the root JSON object assumed to be
* found at jsp->basep . After this call jsp->basep, jsp->out_hrp and
* jsp->userp will all be set to NULL. */
void sgj_finish(sgj_state * jsp);
/* Forms a string of the JSON command line options help and assumes,
* if char_if_not_j is zero, that '-j' is the short form of the of
* the --json[=JO] command line option. */
char * sg_json_usage(int char_if_not_j, char * b, int blen);
/* Convert a byte stream that is meant to be printable ASCII or UTF-8 to
* something that is allowable in a JSON string. This means treating the
* ASCII control characters (i.e. < 0x20) and DEL as specials. Also '\' and
* '"' need to be escaped with a preceding '\'. These C escape codes are used
* in JSON: '\b', '\f', '\n', '\r' and '\t'. Other control characters, and DEL
* are encoded as '\x<hh>' where <hh> is two hex digits. So the DEL and
* null ACSII characters in the input will appear as '\x7f' and '\x00'
* respectively in the output. The output serializer will expand those
* two to '\\x7f' and '\\x00'. Note that the JSON form of '\u<hhhh>' is
* _not_ used. The input is pointed to by 'cup' which is 'ulen' bytes long.
* The output is written to 'op' and will not exceed 'olen_max' bytes. If
* 'olen_max' is breached, this function returns -1 else it returns the
* number of bytes written to 'op'. */
int sgj_conv2json_string(const uint8_t * cup, int ulen, char * op,
int olen_max);
#ifdef __cplusplus
}
#endif
#endif
|