-
Notifications
You must be signed in to change notification settings - Fork 0
/
jprint.txt
186 lines (140 loc) · 6.8 KB
/
jprint.txt
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
jprint - Generate JSON from C
Written by by Fred Weigel, and is open for any use.
https://jsonlint.com/
for json validation
Introduction
jprint is a replacement for jWrite, and other, more complex, methods
of generating JSON. It uses a very simple model, and uses "printf" style
formatting to define a simple DSL (domain specific language) for JSON
production. It does not do any allocations on its own, relying on a
passed-in structure to track nesting. We assume that the caller is able
to reallocate in case of insufficient buffer memory.
Note that jprint DOES NOT PRETTY-PRINT. Use jq (or something like that)
to do that function (if needed). See test.c for examples of use.
jprint only has jp_printf to generate output. Typically, only
three calls are needed: jp_open to initialize, jp_printf to generate
the document and jp_close to validate and "close". jp_close itself is
only an "error check".
The idea of three calls is that jp_printf may be called many times
to produce the desired document. This is particulary useful if an
array is being generated.
Type jprint_t is used to store data for jprint. This is passed to
jprint as a pointer to jprint_t, and contains nesting, buffer and
error information. Up to 32 object/array nesting levels (including
top level) are supported. jp_printf supports "in-line" keys -- these
may be up to 255 characters in length.
#include "jprint.h"
...
jprint_t jp;
char buffer[2048];
int error;
jp_open(&jp, buffer, sizeof buffer);
jp_printf(&jp, "{ integer: %d, real: %g, string: %s }",
10, 1.5, "hello, world");
error = jp_close(&jp);
is a simple sample of use. Note that the json document is pretty much
created in a single call to jp_printf. Note that keys can be specified
"in-line" as here, or with %k format. Compare the previous with:
jp_printf(&jp, "{ %k: %d, real: %g, string: %s }",
"integer", 10, 1.5, "hello, world");
Note that keys are ignored when generating array elements.
"null" is generated:
jp_printf(&jp, "[ %s ]", NULL);
as needed with %s format.
Note that comma (','), colon (':') space and tab are ignored in the
format. This allows the format to more closely match the desired JSON
output. If any of the characters , : space or tab are to be included
in an in-line key, the may be escaped with %. Likewise % can be escaped
with either % or \ (%% or \%). For example, the following key has
embeded spaces:
jp_printf(&jp, "{key\\ with\\ spaces%s}", NULL);
Note that this key does not end with : -- and the %s follows immediately.
This is allowed, but not encouraged.
jprint can be compiled with NO_DOUBLE defined to 1 (in jprint.c). This
removes %g format and double support. Note that ZFS uses this because
of GPL licensing issues.
Format codes supported are %d (int) %u (unsigned), %D (int64_t) and %U
(uint64_t) for numeric. %g is included for double if enabled. If %g
is used and is not enabled, error code JPRINT_NO_DOUBLE is returned.
%b is used for boolean (boolean_t in ZFS). This generates true or false
in the output document.
%s is used for strings. UTF 8 is used. 0x00 through 0x1f and 0x7f are
escaped with the \Uhhhh notation, or the \r \n \t \b \f sequences
defined in the JSON specification. \ and the other "special" JSON
characters are suitably escaped. All other UTF 8 is copied through.
If a NULL is passed as a %s parameter, null is produced in the document.
jprint takes care of ensuring the JSON document is properly structured,
has commas, colons in the correct positions, Strings are quoted and
escaped as needed.
jp_error can be used after jp_printf to return any error code. JPRINT_OK
is returned if everything is in order. JPRINT_BUF_FULL is returned if
the buffer overflowed. This can be used to increase the buffer and
regenerate the document.
As shown in the example the %k format can be used to introduce a dynamic
key. Only strings are supported as keys -- other keys will have to be
converted to strings before use by the using application. This technique
is not illustrated.
The error codes returned can be converted to printable strings with the
jp_errorstring function. As well, each of the jp_printf calls is tracked
by incrementing a call number. This is intended to isolate the jprint
call that generates an error. The call number can be retrieved with the
jp_errorpos function. In general, it is expected that JSON documents
will be generated in pieces (especially arrays). The array will be
opened, and then appended to by subsequent calls to jp_printf. The
array will be close via jp_print("]") (for example). But an error
may have occured in a prior call. This information is conveyed via
jp_errorpos, and is intended to allow the isolation of the error.
Alternately, jp_error could be invoked after each jp_printf.
jp_printf returns -1 on errors, or the number of characters that
this invocation has added to the buffer being built.
If "pretty-printing" is desired, run the output through jq. An example:
{"key":"Hello world!","count":1,"size":4096}
is the document produced by jprint. Note the lack of unnecessary delimiters,
include lack of final newline. It would be possible (in most cases) to
remove the " quoting around the keys -- I think most JSON decoders would
find that acceptable. We put them in. Running this document through jq
provides a pretty-printed canonical form:
echo -n '{"key":"Hello world!","count":1,"size":4096}' | jq
{
"key": "Hello world!",
"count": 2,
"size": 4096
}
Error codes:
#define JPRINT_OK 0
#define JPRINT_BUF_FULL 1 /* output buffer full */
#define JPRINT_NEST_ERROR 2 /* nesting error */
#define JPRINT_STACK_FULL 3 /* array/object nesting */
#define JPRINT_STACK_EMPTY 4 /* stack underflow error */
#define JPRINT_OPEN 5 /* not all objects closed */
#define JPRINT_FMT 6 /* format error */
#define JPRINT_NO_DOUBLE 7 /* %g not supported */
API:
int jp_errorpos(jprint_t *jp);
return error position (call sequence number of jp_printf)
const char *jp_errorstring(int err);
return error string for error code
int jp_error(jprint_t *jp);
return error from jp
void jp_open(jprint_t *jp, char *buffer, size_t buflen);
open json using buffer of length buflen
int jp_close(jprint_t *jp);
close json, returns error
int jp_printf(jprint_t *jp, const char *fmt, ...);
printf items into json, returns -1 or number of characters
added to buffer
%% put % into inline key
%d int
%u unsigned int
%D int64_t
%U uint64_t
%k key
%g double (not supported in kernel)
%b boolean_t
%s char *
{ begin object
} end object
[ begin array
] end array
, : ' ' '\t' pretty format
\ put %,: space tab into inline key