/
githubmirror
/
sg3_utils
Обзор
Документация
Войти
/
githubmirror
/
sg3_utils
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
include/sg_json.h
440 строк
24 KB
Douglas Gilbert
sg_lib: add sg_strscpy() to replace strncpy(); sg_json: add sgj_haj_vs_nex(); fixes from regression testing against version 1.48
23 июн 2026, 06:39
23 июн 2026, 06:39
174643d
Код
Авторство
О чём код?
#ifndef SG_JSON_H #define SG_JSON_H /* * Copyright (c) 2023-2026 Douglas Gilbert. * All rights reserved. * * 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. */ /* This header does not rely on the rest of sg_lib.h . There is an extension * header called sg_json_sg_lib.h that defines some extra JSON support * functions whose implementations do rely on the rest of sg_lib.h . */ /* 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, SGJ_SEP_COLON_1_SPACE_RM_PARENS, }; 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 such argument then they are 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), see out_hrp below */ bool pr_packed; /* 'k' (def: false) only when !pr_pretty */ bool pr_pretty; /* 'p' (def: true) */ bool pr_string; /* 's' (def: true) permit sub-object with 'meaning' */ 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_s C string to the "snake_case" * convention so the output jsan_name_s 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 json_name_s (i.e. the pointer to the output buffer). * Note: strlen(in_name_s) should be <= max_json_name_len . */ char * sgj_convert2snake_rm_parens(const char * in_name_s, char * json_name_s, int max_json_name_len); /* Returns name_so_s 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_name_s, char * json_name_s, int max_json_name_len); /* Is name_s made up of only lower case alphanumerics numbers and * underscores? */ bool sgj_is_snake_name(const char * name_s); /* 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 pr_out_hr is true then human * readable output is placed in the 'plain_text_output' array * pr - has printf() style variadic arguments * _r - suffix indicating the return value should/must be used * nv - adds a name-value JSON field (or several) * val_o - value is a JSON object (or array) * val_i - value is a JSON integer object (int64_t or uint64_t) * val_b - value is a JSON boolean object * s - value is a JSON string object * name_so_s - string (pointer) that will become the name of the (sub) * object created. Assumed to be a conforming "snake" * variant JSON name (only lower case and underscore) * name - string (pointer) that will become the name of the (sub) * object created. If not in a conforming "snake" variant * JSON format, then it is converted into one before be applied. * str - same as s * hex - value is a sequence of bytes in hexadecimal within 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 is valid (i.e. non-NULL) and jsp->pr_as_json is true and * jsp->pr_out_hr is false then this call is ignored (as no human readable * output is required). Otherwise this function prints to stdout or to * the JSON array (jsp->out_hrp), just like printf(fmt, ...); note that no * LF is added. If jsp is valid, jsp->pr_as_json is true, and the * jsp->pr_out_hr is true; then 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->out_hrp . 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 (of the tree structure) */ sgj_opaque_p sgj_new_unattached_string_r(sgj_state * jsp, const char * val_s); sgj_opaque_p sgj_new_unattached_str_len_r(sgj_state * jsp, const char * val_s, int vlen); sgj_opaque_p sgj_new_unattached_integer_r(sgj_state * jsp, uint64_t val_i); sgj_opaque_p sgj_new_unattached_bool_r(sgj_state * jsp, bool val_b); 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 * 'name_so_s' 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 * as NULL indicates an error has occurred. */ sgj_opaque_p sgj_named_subobject_r(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s); 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 * 'name_so_s' 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 as NULL * indicates an error has occurred. */ sgj_opaque_p sgj_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s); sgj_opaque_p sgj_snake_named_subarray_r(sgj_state * jsp, sgj_opaque_p jop, const char * conv2sname); /* If either jsp or val_s 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 'name_so_s' is non-NULL a new named JSON object * is added using 'name_so_s' and the associated value is a JSON string formed * from 'val_s'. If 'name_so_s' is NULL then 'jop' is assumed to be a JSON * array and a JSON string formed from 'val_s' 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 * name_so_s, const char * val_s); sgj_opaque_p sgj_js_nv_s_len(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, const char * val_s, int val_len); /* This variant checks 'va_p' 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 * name_so_s, const uint8_t * va_p, int va_len); /* 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 'name_so_s' is non-NULL a new named JSON object is * added using 'name_so_s' and the associated value is a JSON integer formed * from 'val_i'. If 'name_so_s' is NULL then 'jop' is assumed to be a JSON * array and a JSON integer formed from 'val_i' is added. If successful * returns a pointer newly formed JSON integer. */ sgj_opaque_p sgj_js_nv_i(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i); /* 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 'name_so_s' is non-NULL a new named JSON object is * added using 'name_so_s' and the associated value is a JSON boolean formed * from 'val_b'. If 'name_so_s' is NULL then 'jop' is assumed to be a JSON * array and a JSON boolean formed from 'val_b' is added. If successful returns * a pointer newly formed JSON boolean. */ sgj_opaque_p sgj_js_nv_b(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, bool val_b); /* If jsp is NULL, jsp->pr_as_json is false or ua_jop is NULL, then nothing * happens and NULL is returned. 'jop' is the insertion point but if it is * NULL jsp->basep is used instead. If 'name_so_s' is non-NULL a new named JSON * object is added using 'name_so_s' and the associated value is ua_jop. If * 'name_so_s' 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 prior to 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 * name_so_s, sgj_opaque_p ua_jop /* val_o */); /* 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 the integer 'val_i'. If jsp->pr_hex is true * then that value is two sub-objects, one named 'i' with a 'val_i' as a * JSON integer, the other one named 'hex' with 'val_i' rendered as hex in * a JSON string. If jsp->pr_hex is false then there are no sub-objects and * the 'val_i' is rendered as JSON integer. */ void sgj_js_nv_ihex(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, uint64_t val_i); /* 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 'val_i'. 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 'nmeaning_or_s' with 'vmeaning_or_s' rendered as a * JSON string. If 'nmeaning_or_s' is NULL then "meaning" will be used. If * jsp->pr_string is false then there are no sub-objects and 'val_i' is * rendered as a JSON integer. */ void sgj_js_nv_istr(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i, const char * nmeaning_or_s, const char * vmeaning_or_s); /* Similar to sgj_js_nv_istr(). If jsp->pr_hex is true then 'val_i' is * rendered as ASCII hex in a JSON string. . */ void sgj_js_nv_ihexstr(sgj_state * jsp, sgj_opaque_p jop, const char * name_so_s, int64_t val_i, const char * nmeaning_or_s, const char * vmeaning_or_s); /* This function only produces JSON output if jsp && jsp->pr_as_json are true. * It adds a named object at 'jop' (or jsp->basep if jop is NULL) along with * 'val_i'. If jsp->pr_name_ex is true then 'name_so_s' 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 equivalent 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 * name_so_s, 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 * name_so_s, int64_t val_i, bool hex_as_well, const char * nmeaning_or_s, const char * vmeaning_or_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 'name_so_s' 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 * name_so_s, 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 and 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 * name_so_s, 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 'val_s' 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 jsp->basep is used. If 'name' is NULL then a JSON string object, * made from 'val_s' is added to the JSON array pointed to by 'jop'. * Otherwise a 'name' JSON object whose value is a JSON string object made * from 'val_s' 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 * val_s); void sgj_haj_vs_len(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, const char * val_s, int val_len); void sgj_haj_vs_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, const char * val_s, const char * nex_s); /* Similar to sgj_haj_vs()'s description with 'JSON string object' * replaced by 'JSON integer object'. hex_haj when set will cause val_i * 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 is or-ed with * jsp->pr_hex and the result is used for 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 val_i, 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 val_i, bool hex_haj, const char * vmeaning_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 val_i, 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 val_i, bool hex_haj, const char * vmeaning_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 'val_i'. 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 val_i, 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 val_b); /* Extension of sgj_haj_vs where the value starts at byte_arr and is * num_bytes long. The value is rendered as an ASCII string of bytes * in hexadecimal (two digits per byte) and each byte separated by a * space. The name extra field (nex_s) is ignored if NULL. */ void sgj_haj_vs_hex_bytes_nex(sgj_state * jsp, sgj_opaque_p jop, int leadin_sp, const char * name, enum sgj_separator_t sep, const uint8_t * byte_arr, int num_bytes, const char * nex_s); /* 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. Adds 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" is added and its value * is 'exit_status' value rendered as a JSON integer. The 'exit_status' JSON * object is appended to 'jop' or jsp_basep if 'jop' is NULL. The in-core * JSON tree with jsp->basep as its root is streamed to 'fp'. fp is * assumed to be non-NULL. */ 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 --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 /* SG_JSON_H */