v / vlib / json / json_primitives.c.v
649 lines · 585 sloc · 12.76 KB · 9970ecb8359e59127afad499fa3d3b955a4d38af
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module json
5
6import math
7import strconv
8import time
9
10#flag -I @VEXEROOT/thirdparty/cJSON
11#flag @VEXEROOT/thirdparty/cJSON/cJSON.o
12#include "cJSON.h"
13#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key))
14
15// As cJSON use `libm`, we need to link it.
16$if windows {
17 $if tinyc {
18 #flag @VEXEROOT/thirdparty/tcc/lib/openlibm.o
19 }
20} $else {
21 #flag -lm
22}
23
24pub struct C.cJSON {
25 next &C.cJSON
26 prev &C.cJSON
27 child &C.cJSON
28 type int
29 valuestring &char
30 valueint int
31 valuedouble f64
32}
33
34fn C.cJSON_IsTrue(&C.cJSON) bool
35fn C.cJSON_IsFalse(&C.cJSON) bool
36fn C.cJSON_IsNull(&C.cJSON) bool
37fn C.cJSON_IsNumber(&C.cJSON) bool
38fn C.cJSON_IsString(&C.cJSON) bool
39fn C.cJSON_IsObject(&C.cJSON) bool
40fn C.cJSON_IsArray(&C.cJSON) bool
41
42fn C.cJSON_CreateNumber(f64) &C.cJSON
43fn C.cJSON_CreateRaw(&char) &C.cJSON
44
45fn C.cJSON_CreateBool(bool) &C.cJSON
46
47fn C.cJSON_CreateString(&char) &C.cJSON
48
49fn C.cJSON_CreateRaw(&char) &C.cJSON
50
51fn C.cJSON_Parse(&char) &C.cJSON
52
53fn C.cJSON_PrintUnformatted(&C.cJSON) &char
54
55fn C.cJSON_Print(&C.cJSON) &char
56
57fn C.cJSON_free(voidptr)
58fn C.malloc(usize) voidptr
59fn C.memcpy(voidptr, voidptr, usize) voidptr
60
61// decode tries to decode the provided JSON string, into a V structure.
62// If it can not do that, it returns an error describing the reason for
63// the parsing failure.
64pub fn decode(typ voidptr, s string) !voidptr {
65 // compiler implementation
66 return 0
67}
68
69// encode serialises the provided V value as a JSON string, optimised for shortness.
70pub fn encode(x voidptr) string {
71 // compiler implementation
72 return ''
73}
74
75// encode_pretty serialises the provided V value as a JSON string, in a formatted way, optimised for viewing by humans.
76pub fn encode_pretty(x voidptr) string {
77 // compiler implementation
78 return ''
79}
80
81@[markused]
82fn decode_int(root &C.cJSON) int {
83 if isnil(root) {
84 return 0
85 }
86 return root.valueint
87}
88
89@[markused]
90fn decode_i8(root &C.cJSON) i8 {
91 if isnil(root) {
92 return i8(0)
93 }
94 return i8(root.valueint)
95}
96
97@[markused]
98fn decode_i16(root &C.cJSON) i16 {
99 if isnil(root) {
100 return i16(0)
101 }
102 return i16(root.valueint)
103}
104
105@[markused]
106fn decode_i64(root &C.cJSON) i64 {
107 if isnil(root) {
108 return i64(0)
109 }
110 if value := decode_exact_i64(root) {
111 return value
112 }
113 return i64(root.valuedouble) // i64 is double in C
114}
115
116// TODO: remove when `byte` is removed
117@[markused]
118fn decode_byte(root &C.cJSON) u8 {
119 return decode_u8(root)
120}
121
122@[markused]
123fn decode_u8(root &C.cJSON) u8 {
124 if isnil(root) {
125 return u8(0)
126 }
127 return u8(root.valueint)
128}
129
130@[markused]
131fn decode_u16(root &C.cJSON) u16 {
132 if isnil(root) {
133 return u16(0)
134 }
135 return u16(root.valueint)
136}
137
138@[markused]
139fn decode_u32(root &C.cJSON) u32 {
140 if isnil(root) {
141 return u32(0)
142 }
143 return u32(root.valueint)
144}
145
146@[markused]
147fn decode_u64(root &C.cJSON) u64 {
148 if isnil(root) {
149 return u64(0)
150 }
151 if value := decode_exact_u64(root) {
152 return value
153 }
154 return u64(root.valuedouble)
155}
156
157@[markused]
158fn decode_f32(root &C.cJSON) f32 {
159 if isnil(root) {
160 return f32(0)
161 }
162 return f32(root.valuedouble)
163}
164
165@[markused]
166fn decode_f64(root &C.cJSON) f64 {
167 if isnil(root) {
168 return f64(0)
169 }
170 return root.valuedouble
171}
172
173@[markused]
174fn decode_rune(root &C.cJSON) rune {
175 if isnil(root) {
176 return rune(0)
177 }
178 if isnil(root.valuestring) || !C.cJSON_IsString(root) {
179 return rune(0)
180 }
181
182 // TODO: Parse as runes, bypassing string casting...?
183 return unsafe { tos_clone(&u8(root.valuestring)).runes().first() }
184}
185
186@[markused]
187fn decode_string(root &C.cJSON) string {
188 if isnil(root) {
189 return ''
190 }
191 if !isnil(root.valuestring) && C.cJSON_IsString(root) {
192 return unsafe { tos_clone(&u8(root.valuestring)) } // , _strlen(root.valuestring))
193 }
194 // Object/array values can be stringified JSON payloads (e.g. `{}` from JSON.stringify()).
195 if C.cJSON_IsObject(root) || C.cJSON_IsArray(root) {
196 return json_print(root)
197 }
198 return ''
199}
200
201@[markused]
202fn decode_bool(root &C.cJSON) bool {
203 if isnil(root) {
204 return false
205 }
206 return C.cJSON_IsTrue(root)
207}
208
209@[markused]
210fn decode_time(root &C.cJSON) !time.Time {
211 if isnil(root) || C.cJSON_IsNull(root) {
212 return time.Time{}
213 }
214 mut decoded_time := time.Time{}
215 if C.cJSON_IsString(root) {
216 decoded_time.from_json_string(decode_string(root))!
217 return decoded_time
218 }
219 if C.cJSON_IsNumber(root) {
220 decoded_time.from_json_number(json_print(root))!
221 return decoded_time
222 }
223 return error('expected time.Time to decode from a JSON string or number, got: ${json_print(root)}')
224}
225
226// ///////////////////
227
228@[markused]
229fn encode_int(val int) &C.cJSON {
230 return C.cJSON_CreateNumber(val)
231}
232
233@[markused]
234fn encode_i8(val i8) &C.cJSON {
235 return C.cJSON_CreateNumber(val)
236}
237
238@[markused]
239fn encode_i16(val i16) &C.cJSON {
240 return C.cJSON_CreateNumber(val)
241}
242
243@[markused]
244fn encode_i64(val i64) &C.cJSON {
245 lit := val.str()
246 return C.cJSON_CreateRaw(&char(lit.str))
247}
248
249// TODO: remove when `byte` is removed
250@[markused]
251fn encode_byte(root u8) &C.cJSON {
252 return encode_u8(root)
253}
254
255@[markused]
256fn encode_u8(val u8) &C.cJSON {
257 return C.cJSON_CreateNumber(val)
258}
259
260@[markused]
261fn encode_u16(val u16) &C.cJSON {
262 return C.cJSON_CreateNumber(val)
263}
264
265@[markused]
266fn encode_u32(val u32) &C.cJSON {
267 return C.cJSON_CreateNumber(val)
268}
269
270@[markused]
271fn encode_u64(val u64) &C.cJSON {
272 lit := val.str()
273 return C.cJSON_CreateRaw(&char(lit.str))
274}
275
276@[markused]
277fn encode_f32(val f32) &C.cJSON {
278 return C.cJSON_CreateRaw(&char(json_float_to_raw_string(val).str))
279}
280
281@[markused]
282fn encode_f64(val f64) &C.cJSON {
283 return C.cJSON_CreateRaw(&char(json_float_to_raw_string(val).str))
284}
285
286@[markused]
287fn encode_bool(val bool) &C.cJSON {
288 return C.cJSON_CreateBool(val)
289}
290
291@[markused]
292fn encode_rune(val rune) &C.cJSON {
293 return C.cJSON_CreateRaw(&char(json_ascii_string(val.str()).str))
294}
295
296@[markused]
297fn encode_string(val string) &C.cJSON {
298 return C.cJSON_CreateRaw(&char(json_ascii_string(val).str))
299}
300
301// json_ascii_string returns a quoted JSON string with non-ASCII runes escaped as `\uXXXX`.
302fn json_ascii_string(val string) string {
303 mut output := []u8{cap: val.len + 2}
304 output << `"`
305 for character in val.runes() {
306 match character {
307 `"` {
308 output << `\\`
309 output << `"`
310 }
311 `\\` {
312 output << `\\`
313 output << `\\`
314 }
315 `\b` {
316 output << `\\`
317 output << `b`
318 }
319 `\f` {
320 output << `\\`
321 output << `f`
322 }
323 `\n` {
324 output << `\\`
325 output << `n`
326 }
327 `\r` {
328 output << `\\`
329 output << `r`
330 }
331 `\t` {
332 output << `\\`
333 output << `t`
334 }
335 else {
336 if character < 0x20 || character > 0x7f {
337 if character <= 0xffff {
338 output << `\\`
339 output << `u`
340 hex_string := '${u32(character):04x}'
341 unsafe { output.push_many(hex_string.str, 4) }
342 } else {
343 unicode_point_low := u32(character) - 0x10000
344 surrogate_pair := '\\u${0xD800 + ((unicode_point_low >> 10) & 0x3FF):04X}\\u${
345 0xDC00 + (unicode_point_low & 0x3FF):04x}'
346 unsafe { output.push_many(surrogate_pair.str, surrogate_pair.len) }
347 }
348 } else {
349 output << u8(character)
350 }
351 }
352 }
353 }
354 output << `"`
355 return output.bytestr()
356}
357
358// json_float_to_raw_string uses V's float formatter so json.encode keeps exact float round-trips.
359fn json_float_to_raw_string[T](val T) string {
360 if val == 0 {
361 return '0'
362 }
363 if math.is_nan(f64(val)) || math.is_inf(f64(val), 0) {
364 return 'null'
365 }
366 mut raw := val.str()
367 if raw.len > 2 && raw[raw.len - 2] == `.` && raw[raw.len - 1] == `0` {
368 raw = raw[..raw.len - 2]
369 }
370 return raw
371}
372
373fn decode_exact_i64(root &C.cJSON) ?i64 {
374 if isnil(root) || isnil(root.valuestring) {
375 return none
376 }
377 lit := unsafe { tos_clone(&u8(root.valuestring)) }
378 value := strconv.parse_int(lit, 10, 64) or { return none }
379 return value
380}
381
382fn decode_exact_u64(root &C.cJSON) ?u64 {
383 if isnil(root) || isnil(root.valuestring) {
384 return none
385 }
386 lit := unsafe { tos_clone(&u8(root.valuestring)) }
387 value := strconv.parse_uint(lit, 10, 64) or { return none }
388 return value
389}
390
391fn clone_cstring(s string) &char {
392 buf := C.malloc(usize(s.len + 1))
393 if buf == unsafe { nil } {
394 return unsafe { nil }
395 }
396 unsafe {
397 if s.len > 0 {
398 C.memcpy(buf, s.str, usize(s.len))
399 }
400 (&u8(buf))[s.len] = 0
401 }
402 return &char(buf)
403}
404
405@[inline]
406fn is_json_whitespace(ch u8) bool {
407 return ch == ` ` || ch == `\n` || ch == `\r` || ch == `\t`
408}
409
410fn skip_json_whitespace(src string, idx int) int {
411 mut pos := idx
412 for pos < src.len && is_json_whitespace(src[pos]) {
413 pos++
414 }
415 return pos
416}
417
418fn skip_json_string(src string, idx int) int {
419 mut pos := idx
420 if pos >= src.len || src[pos] != `"` {
421 return -1
422 }
423 pos++
424 for pos < src.len {
425 match src[pos] {
426 `"` {
427 return pos + 1
428 }
429 `\\` {
430 pos++
431 if pos >= src.len {
432 return -1
433 }
434 if src[pos] == `u` {
435 pos += 5
436 } else {
437 pos++
438 }
439 }
440 else {
441 pos++
442 }
443 }
444 }
445 return -1
446}
447
448fn skip_json_literal(src string, lit string, idx int) int {
449 if idx + lit.len > src.len || src[idx..idx + lit.len] != lit {
450 return -1
451 }
452 return idx + lit.len
453}
454
455fn skip_json_number(src string, idx int) (string, int) {
456 mut pos := idx
457 start := pos
458 if pos < src.len && src[pos] == `-` {
459 pos++
460 }
461 if pos >= src.len {
462 return '', -1
463 }
464 if src[pos] == `0` {
465 pos++
466 } else {
467 if !src[pos].is_digit() {
468 return '', -1
469 }
470 for pos < src.len && src[pos].is_digit() {
471 pos++
472 }
473 }
474 if pos < src.len && src[pos] == `.` {
475 pos++
476 if pos >= src.len || !src[pos].is_digit() {
477 return '', -1
478 }
479 for pos < src.len && src[pos].is_digit() {
480 pos++
481 }
482 }
483 if pos < src.len && (src[pos] == `e` || src[pos] == `E`) {
484 pos++
485 if pos < src.len && (src[pos] == `+` || src[pos] == `-`) {
486 pos++
487 }
488 if pos >= src.len || !src[pos].is_digit() {
489 return '', -1
490 }
491 for pos < src.len && src[pos].is_digit() {
492 pos++
493 }
494 }
495 return src[start..pos], pos
496}
497
498fn annotate_json_number_literal(root &C.cJSON, lit string) {
499 if lit.len == 0 || !isnil(root.valuestring) {
500 return
501 }
502 unsafe {
503 root.valuestring = clone_cstring(lit)
504 }
505}
506
507fn annotate_json_value(root &C.cJSON, src string, idx int) int {
508 if isnil(root) {
509 return -1
510 }
511 mut pos := skip_json_whitespace(src, idx)
512 if C.cJSON_IsObject(root) {
513 if pos >= src.len || src[pos] != `{` {
514 return -1
515 }
516 pos++
517 pos = skip_json_whitespace(src, pos)
518 mut child := root.child
519 if isnil(child) {
520 if pos >= src.len || src[pos] != `}` {
521 return -1
522 }
523 return pos + 1
524 }
525 for !isnil(child) {
526 pos = skip_json_string(src, pos)
527 if pos == -1 {
528 return -1
529 }
530 pos = skip_json_whitespace(src, pos)
531 if pos >= src.len || src[pos] != `:` {
532 return -1
533 }
534 pos++
535 pos = annotate_json_value(child, src, pos)
536 if pos == -1 {
537 return -1
538 }
539 pos = skip_json_whitespace(src, pos)
540 if !isnil(child.next) {
541 if pos >= src.len || src[pos] != `,` {
542 return -1
543 }
544 pos++
545 }
546 child = child.next
547 }
548 if pos >= src.len || src[pos] != `}` {
549 return -1
550 }
551 return pos + 1
552 }
553 if C.cJSON_IsArray(root) {
554 if pos >= src.len || src[pos] != `[` {
555 return -1
556 }
557 pos++
558 pos = skip_json_whitespace(src, pos)
559 mut child := root.child
560 if isnil(child) {
561 if pos >= src.len || src[pos] != `]` {
562 return -1
563 }
564 return pos + 1
565 }
566 for !isnil(child) {
567 pos = annotate_json_value(child, src, pos)
568 if pos == -1 {
569 return -1
570 }
571 pos = skip_json_whitespace(src, pos)
572 if !isnil(child.next) {
573 if pos >= src.len || src[pos] != `,` {
574 return -1
575 }
576 pos++
577 }
578 child = child.next
579 }
580 if pos >= src.len || src[pos] != `]` {
581 return -1
582 }
583 return pos + 1
584 }
585 if C.cJSON_IsString(root) {
586 return skip_json_string(src, pos)
587 }
588 if C.cJSON_IsNumber(root) {
589 lit, next_pos := skip_json_number(src, pos)
590 if next_pos == -1 {
591 return -1
592 }
593 annotate_json_number_literal(root, lit)
594 return next_pos
595 }
596 if C.cJSON_IsTrue(root) {
597 return skip_json_literal(src, 'true', pos)
598 }
599 if C.cJSON_IsFalse(root) {
600 return skip_json_literal(src, 'false', pos)
601 }
602 if C.cJSON_IsNull(root) {
603 return skip_json_literal(src, 'null', pos)
604 }
605 return -1
606}
607
608fn annotate_json_number_literals(root &C.cJSON, src string) {
609 _ := annotate_json_value(root, src, 0)
610}
611
612// ///////////////////////
613// user := decode_User(json_parse(js_string_var))
614@[markused]
615fn json_parse(s string) &C.cJSON {
616 root := C.cJSON_Parse(&char(s.str))
617 if !isnil(root) {
618 annotate_json_number_literals(root, s)
619 }
620 return root
621}
622
623// json_string := json_print(encode_User(user))
624@[markused]
625fn json_print(data &C.cJSON) string {
626 s := C.cJSON_PrintUnformatted(data)
627 if s == unsafe { nil } {
628 return ''
629 }
630 r := unsafe { tos_clone(&u8(s)) }
631 C.cJSON_free(s)
632 return r
633}
634
635@[markused]
636fn json_print_pretty(data &C.cJSON) string {
637 s := C.cJSON_Print(data)
638 if s == unsafe { nil } {
639 return ''
640 }
641 r := unsafe { tos_clone(&u8(s)) }
642 C.cJSON_free(s)
643 return r
644}
645
646// / cjson wrappers
647// fn json_array_for_each(val, root &C.cJSON) {
648// #cJSON_ArrayForEach (val ,root)
649// }
650