Lite³
A JSON-Compatible Zero-Copy Serialization Format
Loading...
Searching...
No Matches
json_enc.c
1/*
2 Lite³: A JSON-Compatible Zero-Copy Serialization Format
3
4 Copyright © 2025 Elias de Jong <elias@fastserial.com>
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23
24 __ __________________ ____
25 _ ___ ___/ /___(_)_/ /_______|_ /
26 _ _____/ / __/ /_ __/ _ \_/_ <
27 ___ __/ /___/ / / /_ / __/____/
28 /_____/_/ \__/ \___/
29*/
30#include "lite3.h"
31
32
33
34#ifdef LITE3_JSON
35#include <stdio.h>
36#include <string.h>
37#include <stdint.h>
38#include <errno.h>
39
40#include "yyjson/yyjson.h"
41#include "nibble_base64/base64.h"
42
43
44
45// Typedef for primitive types
46typedef float f32;
47typedef double f64;
48typedef int8_t i8;
49typedef uint8_t u8;
50typedef int16_t i16;
51typedef uint16_t u16;
52typedef int32_t i32;
53typedef uint32_t u32;
54typedef int64_t i64;
55typedef uint64_t u64;
56
57
58
59// Forward declarations
60int _lite3_json_enc_obj(const unsigned char *buf, size_t buflen, size_t ofs, size_t nesting_depth, yyjson_mut_doc *doc, yyjson_mut_val *coll);
61int _lite3_json_enc_arr(const unsigned char *buf, size_t buflen, size_t ofs, size_t nesting_depth, yyjson_mut_doc *doc, yyjson_mut_val *coll);
62
63int _lite3_json_enc_switch(const unsigned char *buf, size_t buflen, size_t nesting_depth, yyjson_mut_doc *doc, yyjson_mut_val **yy_val, lite3_val *val)
64{
65 enum lite3_type type = lite3_val_type(val);
66 switch (type) {
67 case LITE3_TYPE_NULL:
68 *yy_val = yyjson_mut_null(doc);
69 break;
70 case LITE3_TYPE_BOOL:
71 *yy_val = yyjson_mut_bool(doc, lite3_val_bool(val));
72 break;
73 case LITE3_TYPE_I64:
74 *yy_val = yyjson_mut_sint(doc, lite3_val_i64(val));
75 break;
76 case LITE3_TYPE_F64:
77 *yy_val = yyjson_mut_double(doc, lite3_val_f64(val));
78 break;
80 size_t bytes_len;
81 const u8 *bytes = lite3_val_bytes(val, &bytes_len);
82 int b64_len;
83 char *b64 = nibble_base64(bytes, (int)bytes_len, &b64_len);
84 if (!b64) {
85 LITE3_PRINT_ERROR("FAILED TO CONVERT BYTES TO BASE64\n");
86 // No need to free the `b64` pointer, since the allocation would have failed anyways.
87 return -1;
88 }
89 /*
90 According to `yyjson` docs:
91 yyjson_mut_strn(...) --> "The input string is not copied, you should keep this string unmodified for the lifetime of this JSON document."
92 yyjson_mut_strncpy(...) --> "The input string is copied and held by the document."
93
94 So we must use `yyjson_mut_strncpy(...)` here so we can free the temporary base64 buffer.
95 NOTE: `b64_len` does NOT contain the NULL terminator. `yyjson` docs say: "The `str` should be a UTF-8 string, null-terminator is not required."
96 */
97 *yy_val = yyjson_mut_strncpy(doc, b64, (size_t)b64_len);
98 free(b64);
99 break;
101 size_t str_len;
102 const char *str = lite3_val_str_n(val, &str_len);
103 /*
104 Here we can use `yyjson_mut_strn(...)` since the value will remain backed up by the Lite³ buffer for the duration of the JSON-conversion process.
105 `str_len` excludes the NULL terminator. `yyjson` docs say: "The `str` should be a UTF-8 string, null-terminator is not required."
106 */
107 *yy_val = yyjson_mut_strn(doc, str, str_len);
108 break;
110 *yy_val = yyjson_mut_obj(doc);
111 size_t obj_ofs = (size_t)((u8 *)val - buf);
112 if (_lite3_json_enc_obj(buf, buflen, obj_ofs, nesting_depth, doc, *yy_val) < 0)
113 return -1;
114 break;
115 case LITE3_TYPE_ARRAY:
116 *yy_val = yyjson_mut_arr(doc);
117 size_t arr_ofs = (size_t)((u8 *)val - buf);
118 if (_lite3_json_enc_arr(buf, buflen, arr_ofs, nesting_depth, doc, *yy_val) < 0)
119 return -1;
120 break;
121 default:
122 LITE3_PRINT_ERROR("FAILED TO BUILD JSON DOCUMENT: VALUE TYPE INVALID\n");
123 errno = EINVAL;
124 return -1;
125 }
126 return 0;
127}
128
129/*
130 Function that recursively builds a JSON document.
131 - Returns 0 on success
132 - Returns < 0 on error
133*/
134int _lite3_json_enc_obj(const unsigned char *buf, size_t buflen, size_t ofs, size_t nesting_depth, yyjson_mut_doc *doc, yyjson_mut_val *coll)
135{
136 if (++nesting_depth > LITE3_JSON_NESTING_DEPTH_MAX) {
137 LITE3_PRINT_ERROR("FAILED TO BUILD JSON DOCUMENT: nesting_depth > LITE3_JSON_NESTING_DEPTH_MAX\n");
138 errno = EINVAL;
139 return -1;
140 }
141 lite3_iter iter;
142 int ret;
143 if ((ret = lite3_iter_create(buf, buflen, ofs, &iter)) < 0)
144 return ret;
145 lite3_str key;
146 lite3_val *val;
147 size_t val_ofs;
148 yyjson_mut_val *yy_val;
149 while ((ret = lite3_iter_next(buf, buflen, &iter, &key, &val_ofs)) == LITE3_ITER_ITEM) {
150 val = (lite3_val *)(buf + val_ofs);
151 if ((ret = _lite3_json_enc_switch(buf, buflen, nesting_depth, doc, &yy_val, val)) < 0)
152 return ret;
153 if (!yyjson_mut_obj_add(coll, yyjson_mut_str(doc, LITE3_STR(buf, key)), yy_val)) {
154 LITE3_PRINT_ERROR("FAILED TO BUILD JSON DOCUMENT: ADDING KEY-VALUE PAIR FAILED\n");
155 errno = EINVAL;
156 return -1;
157 }
158 }
159 return ret;
160}
161
162/*
163 Function that recursively builds a JSON document.
164 - Returns 0 on success
165 - Returns < 0 on error
166*/
167int _lite3_json_enc_arr(const unsigned char *buf, size_t buflen, size_t ofs, size_t nesting_depth, yyjson_mut_doc *doc, yyjson_mut_val *coll)
168{
169 if (++nesting_depth > LITE3_JSON_NESTING_DEPTH_MAX) {
170 LITE3_PRINT_ERROR("FAILED TO BUILD JSON DOCUMENT: nesting_depth > LITE3_JSON_NESTING_DEPTH_MAX\n");
171 errno = EINVAL;
172 return -1;
173 }
174 lite3_iter iter;
175 int ret;
176 if ((ret = lite3_iter_create(buf, buflen, ofs, &iter)) < 0)
177 return ret;
178 lite3_val *val;
179 size_t val_ofs;
180 yyjson_mut_val *yy_val;
181 while ((ret = lite3_iter_next(buf, buflen, &iter, NULL, &val_ofs)) == LITE3_ITER_ITEM) {
182 val = (lite3_val *)(buf + val_ofs);
183 if ((ret = _lite3_json_enc_switch(buf, buflen, nesting_depth, doc, &yy_val, val)) < 0)
184 return ret;
185 if (!yyjson_mut_arr_append(coll, yy_val)) {
186 LITE3_PRINT_ERROR("FAILED TO BUILD JSON DOCUMENT: APPENDING ARRAY ELEMENT FAILED\n");
187 errno = EINVAL;
188 return -1;
189 }
190 }
191 return ret;
192}
193
194yyjson_mut_doc *_lite3_json_enc_doc(const unsigned char *buf, size_t buflen, size_t ofs)
195{
196 if (_lite3_verify_get(buf, buflen, ofs) < 0)
197 return NULL;
198 yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL);
199 if (!doc)
200 return NULL;
201 yyjson_mut_val *root;
202 switch (*(buf + ofs)) {
204 root = yyjson_mut_obj(doc);
205 if (_lite3_json_enc_obj(buf, buflen, ofs, 0, doc, root) < 0)
206 goto error;
207 break;
208 case LITE3_TYPE_ARRAY:
209 root = yyjson_mut_arr(doc);
210 if (_lite3_json_enc_arr(buf, buflen, ofs, 0, doc, root) < 0)
211 goto error;
212 break;
213 default:
214 LITE3_PRINT_ERROR("INVALID ARGUMENT: EXPECTING ARRAY OR OBJECT TYPE\n");
215 errno = EINVAL;
216 goto error;
217 }
218 yyjson_mut_doc_set_root(doc, root);
219 return doc;
220error:
221 yyjson_mut_doc_free(doc);
222 return NULL;
223}
224
225int lite3_json_print(const unsigned char *buf, size_t buflen, size_t ofs)
226{
227 yyjson_mut_doc *doc = _lite3_json_enc_doc(buf, buflen, ofs);
228 if (!doc)
229 return -1;
230 size_t len;
231 yyjson_write_err err;
232 char *json = yyjson_mut_write_opts(doc, YYJSON_WRITE_PRETTY, NULL, &len, &err);
233 yyjson_mut_doc_free(doc);
234
235 if (!json) {
236 LITE3_PRINT_ERROR("FAILED TO WRITE JSON\tyyjson error code: %u msg:%s\n", err.code, err.msg);
237 errno = EIO;
238 return -1;
239 }
240 fwrite(json, 1, len, stdout);
241 fputc('\n', stdout);
242 free(json);
243 return 0;
244}
245
246char *lite3_json_enc(const unsigned char *buf, size_t buflen, size_t ofs, size_t *restrict out_len)
247{
248 yyjson_mut_doc *doc = _lite3_json_enc_doc(buf, buflen, ofs);
249 if (!doc)
250 return NULL;
251 yyjson_write_err err;
252 char *json = yyjson_mut_write_opts(doc, YYJSON_WRITE_NOFLAG, NULL, out_len, &err);
253 yyjson_mut_doc_free(doc);
254 if (!json) {
255 LITE3_PRINT_ERROR("FAILED TO WRITE JSON\tyyjson error code: %u msg:%s\n", err.code, err.msg);
256 errno = EIO;
257 return NULL;
258 }
259 return json;
260}
261
262char *lite3_json_enc_pretty(const unsigned char *buf, size_t buflen, size_t ofs, size_t *restrict out_len)
263{
264 yyjson_mut_doc *doc = _lite3_json_enc_doc(buf, buflen, ofs);
265 if (!doc)
266 return NULL;
267 yyjson_write_err err;
268 char *json = yyjson_mut_write_opts(doc, YYJSON_WRITE_PRETTY, NULL, out_len, &err);
269 yyjson_mut_doc_free(doc);
270 if (!json) {
271 LITE3_PRINT_ERROR("FAILED TO WRITE JSON\tyyjson error code: %u msg:%s\n", err.code, err.msg);
272 errno = EIO;
273 return NULL;
274 }
275 return json;
276}
277
278int64_t lite3_json_enc_buf(const unsigned char *buf, size_t buflen, size_t ofs, char *restrict json_buf, size_t json_bufsz)
279{
280 yyjson_mut_doc *doc = _lite3_json_enc_doc(buf, buflen, ofs);
281 if (!doc)
282 return -1;
283 yyjson_write_err err;
284 size_t ret = yyjson_mut_write_buf(json_buf, json_bufsz, doc, YYJSON_WRITE_NOFLAG, &err);
285 assert(ret <= INT64_MAX);
286 yyjson_mut_doc_free(doc);
287 if (ret == 0) {
288 LITE3_PRINT_ERROR("FAILED TO WRITE JSON\tyyjson error code: %u msg:%s\n", err.code, err.msg);
289 errno = EIO;
290 return -1;
291 }
292 return (i64)ret;
293}
294
295int64_t lite3_json_enc_buf_pretty(const unsigned char *buf, size_t buflen, size_t ofs, char *restrict json_buf, size_t json_bufsz)
296{
297 yyjson_mut_doc *doc = _lite3_json_enc_doc(buf, buflen, ofs);
298 if (!doc)
299 return -1;
300 yyjson_write_err err;
301 size_t ret = yyjson_mut_write_buf(json_buf, json_bufsz, doc, YYJSON_WRITE_PRETTY, &err);
302 assert(ret <= INT64_MAX);
303 yyjson_mut_doc_free(doc);
304 if (ret == 0) {
305 LITE3_PRINT_ERROR("FAILED TO WRITE JSON\tyyjson error code: %u msg:%s\n", err.code, err.msg);
306 errno = EIO;
307 return -1;
308 }
309 return (i64)ret;
310}
311#endif // LITE3_JSON
#define LITE3_JSON_NESTING_DEPTH_MAX
Maximum nesting limit for JSON documents being encoded or decoded.
Definition lite3.h:2828
#define LITE3_ITER_ITEM
Return value of lite3_iter_next(); iterator produced an item, continue;.
Definition lite3.h:2616
int lite3_iter_next(const unsigned char *buf, size_t buflen, lite3_iter *iter, lite3_str *out_key, size_t *out_val_ofs)
Get the next item from a lite3 iterator.
Definition lite3.c:333
static int lite3_iter_create(const unsigned char *buf, size_t buflen, size_t ofs, lite3_iter *out)
Create a lite3 iterator for the given object or array.
Definition lite3.h:2644
char * lite3_json_enc_pretty(const unsigned char *buf, size_t buflen, size_t ofs, size_t *__restrict out_len)
Convert Lite³ to JSON prettified string.
int lite3_json_print(const unsigned char *buf, size_t buflen, size_t ofs)
Print Lite³ buffer as JSON to stdout
int64_t lite3_json_enc_buf(const unsigned char *buf, size_t buflen, size_t ofs, char *__restrict json_buf, size_t json_bufsz)
Convert Lite³ to JSON and write to output buffer.
char * lite3_json_enc(const unsigned char *buf, size_t buflen, size_t ofs, size_t *__restrict out_len)
Convert Lite³ to JSON string.
lite3_type
enum containing all Lite³ types
Definition lite3.h:510
#define LITE3_STR(buf, val)
Generational pointer / safe access wrapper.
Definition lite3.h:666
@ LITE3_TYPE_ARRAY
maps to 'array' type in JSON
Definition lite3.h:518
@ LITE3_TYPE_STRING
maps to 'string' type in JSON
Definition lite3.h:516
@ LITE3_TYPE_BOOL
maps to 'boolean' type in JSON; underlying datatype: bool
Definition lite3.h:512
@ LITE3_TYPE_BYTES
coverted to base64 string in JSON
Definition lite3.h:515
@ LITE3_TYPE_F64
maps to 'number' type in JSON; underlying datatype: double
Definition lite3.h:514
@ LITE3_TYPE_OBJECT
maps to 'object' type in JSON
Definition lite3.h:517
@ LITE3_TYPE_I64
maps to 'number' type in JSON; underlying datatype: int64_t
Definition lite3.h:513
@ LITE3_TYPE_NULL
maps to 'null' type in JSON
Definition lite3.h:511
static const char * lite3_val_str_n(lite3_val *val, size_t *out_len)
Definition lite3.h:2757
static enum lite3_type lite3_val_type(lite3_val *val)
Returns the value type of *val
Definition lite3.h:2694
Lite³ Buffer API Header.
Struct containing iterator state.
Definition lite3.h:2626
Struct holding a reference to a string inside a Lite³ buffer.
Definition lite3.h:601
Struct representing a value inside a Lite³ buffer.
Definition lite3.h:531