File: sg_json.h

package info (click to toggle)
sg3-utils 1.48-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,212 kB
  • sloc: ansic: 123,509; cpp: 14,381; sh: 7,956; makefile: 702
file content (405 lines) | stat: -rw-r--r-- 22,177 bytes parent folder | download | duplicates (2)
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