v
Зеркало из https://github.com/vlang/v
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 json2
5
6import time
7
8// Encoder encodes the an `Any` type into JSON representation.
9// It provides parameters in order to change the end result.
10pub struct Encoder {
11pub:
12newline u8
13newline_spaces_count int
14escape_unicode bool = true
15}
16
17// byte array versions of the most common tokens/chars to avoid reallocations
18const null_in_bytes = 'null'
19
20const true_in_string = 'true'
21
22const false_in_string = 'false'
23
24const empty_array = [u8(`[`), `]`]!
25
26const comma_rune = `,`
27
28const colon_rune = `:`
29
30const quote_rune = `"`
31
32const back_slash = [u8(`\\`), `\\`]!
33
34const quote = [u8(`\\`), `"`]!
35
36const slash = [u8(`\\`), `/`]!
37
38const null_unicode = [u8(`\\`), `u`, `0`, `0`, `0`, `0`]!
39
40const ascii_control_characters = ['\\u0000', '\\t', '\\n', '\\r', '\\u0004', '\\u0005', '\\u0006',
41'\\u0007', '\\b', '\\t', '\\n', '\\u000b', '\\f', '\\r', '\\u000e', '\\u000f', '\\u0010',
42'\\u0011', '\\u0012', '\\u0013', '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018', '\\u0019',
43'\\u001a', '\\u001b', '\\u001c', '\\u001d', '\\u001e', '\\u001f']!
44
45const curly_open_rune = `{`
46
47const curly_close_rune = `}`
48
49const ascii_especial_characters = [u8(`\\`), `"`, `/`]!
50
51// encode is a generic function that encodes a type into a JSON string.
52@[manualfree]
53pub fn encode[T](val T) string {
54$if T is $array {
55return encode_array(val)
56} $else {
57mut count := Count{0}
58count.count_chars(val)
59
60mut buf := []u8{cap: count.total}
61
62defer {
63unsafe { buf.free() }
64}
65encoder := Encoder{}
66
67encoder.encode_value(val, mut buf) or {
68println(err)
69encoder.encode_value[string]('null', mut buf) or {}
70}
71
72return buf.bytestr()
73}
74}
75
76// encode_array is a generic function that encodes a array into a JSON string.
77@[manualfree]
78fn encode_array[T](val []T) string {
79if val.len == 0 {
80return '[]'
81}
82
83mut buf := []u8{}
84
85defer {
86unsafe { buf.free() }
87}
88
89encoder := Encoder{}
90encoder.encode_array(val, 1, mut buf) or {
91println(err)
92encoder.encode_value[string]('null', mut buf) or {}
93}
94
95return buf.bytestr()
96}
97
98// encode_pretty ...
99pub fn encode_pretty[T](typed_data T) string {
100encoded := encode(typed_data)
101raw_decoded := raw_decode(encoded) or { 0 }
102return raw_decoded.prettify_json_str()
103}
104
105// encode_value encodes a value to the specific buffer.
106pub fn (e &Encoder) encode_value[T](val T, mut buf []u8) ! {
107e.encode_value_with_level[T](val, 1, mut buf)!
108}
109
110fn (e &Encoder) encode_newline(level int, mut buf []u8) ! {
111if e.newline != 0 {
112buf << e.newline
113for j := 0; j < level * e.newline_spaces_count; j++ {
114buf << ` `
115}
116}
117}
118
119fn (e &Encoder) encode_map[T](value T, level int, mut buf []u8) ! {
120buf << curly_open_rune
121mut idx := 0
122for k, v in value {
123e.encode_newline(level, mut buf)!
124// e.encode_string(k.str(), mut buf)!
125e.encode_string(k, mut buf)!
126buf << colon_rune
127if e.newline != 0 {
128buf << ` `
129}
130
131// workaround to avoid `cannot convert 'struct x__json2__Any' to 'struct string'`
132$if v is $sumtype {
133$for variant_value in v.variants {
134if v is variant_value {
135e.encode_value_with_level(v, level + 1, mut buf)!
136}
137}
138} $else {
139e.encode_value_with_level(v, level + 1, mut buf)!
140}
141
142if idx < value.len - 1 {
143buf << comma_rune
144}
145idx++
146}
147// e.encode_newline(level, mut buf)!
148e.encode_newline(level - 1, mut buf)!
149buf << curly_close_rune
150}
151
152fn (e &Encoder) encode_value_with_level[T](val T, level int, mut buf []u8) ! {
153$if val is $option {
154workaround := val
155if workaround != none {
156e.encode_value_with_level(val, level, mut buf)!
157}
158} $else $if T is string {
159e.encode_string(val, mut buf)!
160} $else $if T is $sumtype {
161$for v in val.variants {
162if val is v {
163e.encode_value_with_level(val, level, mut buf)!
164}
165}
166} $else $if T is $alias {
167// TODO
168} $else $if T is time.Time {
169str_value := val.format_rfc3339()
170buf << quote_rune
171unsafe { buf.push_many(str_value.str, str_value.len) }
172buf << quote_rune
173} $else $if T is $map {
174e.encode_map(val, level, mut buf)!
175} $else $if T is $array {
176e.encode_array(val, level, mut buf)!
177} $else $if T is Encodable {
178str_value := val.json_str()
179unsafe { buf.push_many(str_value.str, str_value.len) }
180} $else $if T is Null {
181unsafe { buf.push_many(null_in_bytes.str, null_in_bytes.len) }
182} $else $if T is $struct {
183e.encode_struct(val, level, mut buf)!
184} $else $if T is $enum {
185str_int := int(val).str()
186unsafe { buf.push_many(str_int.str, str_int.len) }
187} $else $if T is $int || T is $float || T is bool {
188str_int := val.str()
189unsafe { buf.push_many(str_int.str, str_int.len) }
190} $else {
191return error('cannot encode value with ${typeof(val).name} type')
192}
193}
194
195fn (e &Encoder) encode_struct[U](val U, level int, mut buf []u8) ! {
196buf << curly_open_rune
197mut i := 0
198mut fields_len := 0
199
200$for field in U.fields {
201mut @continue := false
202for attr in field.attrs {
203if attr.contains('skip') {
204@continue = true
205}
206if attr.contains('json: ') {
207if attr.replace('json: ', '') == '-' {
208@continue = true
209}
210break
211}
212}
213if !@continue {
214$if field.is_option {
215if val.$(field.name) != none {
216fields_len++
217}
218} $else {
219fields_len++
220}
221}
222}
223$for field in U.fields {
224mut ignore_field := false
225
226value := val.$(field.name)
227
228is_nil := val.$(field.name).str() == '&nil'
229
230mut json_name := ''
231
232for attr in field.attrs {
233if attr.contains('skip') {
234ignore_field = true
235}
236if attr.contains('json: ') {
237json_name = attr.replace('json: ', '')
238if json_name == '-' {
239ignore_field = true
240}
241break
242}
243}
244
245if !ignore_field {
246$if value is $option {
247workaround := val.$(field.name)
248if workaround != none { // smartcast
249e.encode_newline(level, mut buf)!
250if json_name != '' {
251e.encode_string(json_name, mut buf)!
252} else {
253e.encode_string(field.name, mut buf)!
254}
255buf << colon_rune
256
257if e.newline != 0 {
258buf << ` `
259}
260e.encode_value_with_level(value, level, mut buf)!
261} else {
262ignore_field = true
263}
264} $else {
265is_none := val.$(field.name).str() == 'unknown sum type value' // assert json.encode(StructType[SumTypes]{}) == '{}'
266if !is_none && !is_nil {
267e.encode_newline(level, mut buf)!
268if json_name != '' {
269e.encode_string(json_name, mut buf)!
270} else {
271e.encode_string(field.name, mut buf)!
272}
273buf << colon_rune
274
275if e.newline != 0 {
276buf << ` `
277}
278}
279
280$if field.indirections != 0 {
281if val.$(field.name) != unsafe { nil } {
282$if field.indirections == 1 {
283e.encode_value_with_level(*val.$(field.name), level + 1, mut
284buf)!
285}
286$if field.indirections == 2 {
287e.encode_value_with_level(**val.$(field.name), level + 1, mut
288buf)!
289}
290$if field.indirections == 3 {
291e.encode_value_with_level(***val.$(field.name), level + 1, mut
292buf)!
293}
294}
295} $else $if field.typ is string {
296e.encode_string(val.$(field.name).str(), mut buf)!
297} $else $if field.typ is time.Time {
298str_value := val.$(field.name).format_rfc3339()
299buf << quote_rune
300unsafe { buf.push_many(str_value.str, str_value.len) }
301buf << quote_rune
302} $else $if field.typ is bool {
303if value {
304unsafe { buf.push_many(true_in_string.str, true_in_string.len) }
305} else {
306unsafe { buf.push_many(false_in_string.str, false_in_string.len) }
307}
308} $else $if field.typ in [$float, $int] {
309str_value := val.$(field.name).str()
310unsafe { buf.push_many(str_value.str, str_value.len) }
311} $else $if field.is_array {
312// TODO: replace for `field.typ is $array`
313e.encode_array(value, level + 1, mut buf)!
314} $else $if field.typ is $array {
315// e.encode_array(value, level + 1, mut buf)! // FIXME: error: could not infer generic type `U` in call to `encode_array`
316} $else $if field.typ is $struct {
317e.encode_struct(value, level + 1, mut buf)!
318} $else $if field.is_map {
319e.encode_map(value, level + 1, mut buf)!
320} $else $if field.is_enum {
321// TODO: replace for `field.typ is $enum`
322// str_value := int(val.$(field.name)).str()
323// unsafe { buf.push_many(str_value.str, str_value.len) }
324e.encode_value_with_level(val.$(field.name), level + 1, mut buf)!
325} $else $if field.typ is $enum {
326} $else $if field.typ is $sumtype {
327field_value := val.$(field.name)
328if field_value.str() != 'unknown sum type value' {
329$for v in field_value.variants {
330if field_value is v {
331e.encode_value_with_level(field_value, level, mut buf)!
332}
333}
334}
335} $else $if field.typ is $alias {
336$if field.unaliased_typ is string {
337e.encode_string(val.$(field.name).str(), mut buf)!
338} $else $if field.unaliased_typ is time.Time {
339parsed_time := time.parse(val.$(field.name).str()) or { time.Time{} }
340e.encode_string(parsed_time.format_rfc3339(), mut buf)!
341} $else $if field.unaliased_typ is bool {
342if val.$(field.name) {
343unsafe { buf.push_many(true_in_string.str, true_in_string.len) }
344} else {
345unsafe { buf.push_many(false_in_string.str, false_in_string.len) }
346}
347} $else $if field.unaliased_typ in [$float, $int] {
348str_value := val.$(field.name).str()
349unsafe { buf.push_many(str_value.str, str_value.len) }
350} $else $if field.unaliased_typ is $array {
351// TODO
352} $else $if field.unaliased_typ is $struct {
353e.encode_struct(value, level + 1, mut buf)!
354} $else $if field.unaliased_typ is $enum {
355// TODO
356} $else $if field.unaliased_typ is $sumtype {
357// TODO
358} $else {
359return error('the alias ${typeof(val).name} cannot be encoded')
360}
361} $else {
362return error('type ${typeof(val).name} cannot be array encoded')
363}
364}
365}
366
367if i < fields_len - 1 && !ignore_field {
368if !is_nil {
369buf << comma_rune
370}
371}
372if !ignore_field {
373i++
374}
375}
376e.encode_newline(level - 1, mut buf)!
377buf << curly_close_rune
378// b.measure('encode_struct')
379}
380
381fn (e &Encoder) encode_array[U](val []U, level int, mut buf []u8) ! {
382if val.len == 0 {
383unsafe { buf.push_many(&empty_array[0], empty_array.len) }
384return
385}
386buf << `[`
387for i in 0 .. val.len {
388e.encode_newline(level, mut buf)!
389
390$if U is string || U is bool || U is $int || U is $float {
391e.encode_value_with_level(val[i], level + 1, mut buf)!
392} $else $if U is $array {
393e.encode_array(val[i], level + 1, mut buf)!
394} $else $if U is $struct {
395e.encode_struct(val[i], level + 1, mut buf)!
396} $else $if U is $sumtype {
397e.encode_value_with_level(val[i], level + 1, mut buf)!
398} $else $if U is $enum {
399// TODO: test
400e.encode_value_with_level(val[i], level + 1, mut buf)!
401} $else {
402return error('type ${typeof(val).name} cannot be array encoded')
403}
404if i < val.len - 1 {
405buf << comma_rune
406}
407}
408
409e.encode_newline(level - 1, mut buf)!
410buf << `]`
411}
412
413// str returns the JSON string representation of the `map[string]Any` type.
414pub fn (f map[string]Any) str() string {
415return Any(f).json_str()
416}
417
418// str returns the JSON string representation of the `[]Any` type.
419pub fn (f []Any) str() string {
420return Any(f).json_str()
421}
422
423// str returns the string representation of the `Any` type. Use the `json_str` method
424// if you want to use the escaped str() version of the `Any` type.
425pub fn (f Any) str() string {
426if f is string {
427return f
428} else {
429return f.json_str()
430}
431}
432
433// json_str returns the JSON string representation of the `Any` type.
434pub fn (f Any) json_str() string {
435return encode(f)
436}
437
438// prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type.
439@[manualfree]
440pub fn (f Any) prettify_json_str() string {
441mut buf := []u8{}
442defer {
443unsafe { buf.free() }
444}
445mut enc := Encoder{
446newline: `\n`
447newline_spaces_count: 2
448}
449enc.encode_value(f, mut buf) or {}
450return buf.bytestr()
451}
452
453// TODO: Need refactor. Is so slow. The longer the string, the lower the performance.
454// encode_string returns the JSON spec-compliant version of the string.
455@[direct_array_access]
456fn (e &Encoder) encode_string(s string, mut buf []u8) ! {
457if s == '' {
458empty := [u8(quote_rune), quote_rune]!
459unsafe { buf.push_many(&empty[0], 2) }
460return
461}
462mut last_no_buffer_expansible_char_position_candidate := 0
463buf << quote_rune
464
465if !e.escape_unicode {
466unsafe {
467buf.push_many(s.str, s.len)
468buf << quote_rune
469}
470return
471}
472
473for idx := 0; idx < s.len; idx++ {
474current_byte := s[idx]
475
476mut current_utf8_len := ((0xe5000000 >> ((current_byte >> 3) & 0x1e)) & 3) + 1
477
478current_value_cause_buffer_expansion :=
479(current_utf8_len == 1 && ((current_byte < 32 || current_byte > 127)
480|| current_byte in ascii_especial_characters)) || current_utf8_len == 3
481
482if !current_value_cause_buffer_expansion {
483// while it is not the last one
484if idx < s.len - 1 {
485if s.len > (idx + current_utf8_len) {
486if current_utf8_len == 2 || current_utf8_len == 4 {
487// runes like: ã, ü, etc.
488// Emojis ranges
489// (0x1F300, 0x1F5FF), # Miscellaneous Symbols and Pictographs
490// (0x1F600, 0x1F64F), # Emoticons
491// (0x1F680, 0x1F6FF), # Transport and Map Symbols
492idx += current_utf8_len - 1
493continue
494}
495} else {
496unsafe {
497buf.push_many(s.str + last_no_buffer_expansible_char_position_candidate,
498s.len - last_no_buffer_expansible_char_position_candidate)
499}
500break
501}
502} else if idx == s.len - 1 {
503unsafe {
504buf.push_many(s.str + last_no_buffer_expansible_char_position_candidate,
505s.len - last_no_buffer_expansible_char_position_candidate)
506}
507}
508} else {
509if idx > 0 {
510length := idx - last_no_buffer_expansible_char_position_candidate
511unsafe {
512buf.push_many(s.str + last_no_buffer_expansible_char_position_candidate,
513length)
514}
515last_no_buffer_expansible_char_position_candidate = idx + 1
516}
517}
518
519if current_utf8_len == 1 {
520if current_byte < 32 {
521// ASCII Control Characters
522unsafe {
523buf.push_many(ascii_control_characters[current_byte].str, ascii_control_characters[current_byte].len)
524}
525last_no_buffer_expansible_char_position_candidate = idx + 1
526} else if current_byte >= 32 && current_byte < 128 {
527// ASCII especial characters
528if current_byte == `\\` {
529unsafe { buf.push_many(&back_slash[0], back_slash.len) }
530last_no_buffer_expansible_char_position_candidate = idx + 1
531continue
532} else if current_byte == `"` {
533unsafe { buf.push_many("e[0], quote.len) }
534last_no_buffer_expansible_char_position_candidate = idx + 1
535continue
536} else if current_byte == `/` {
537unsafe { buf.push_many(&slash[0], slash.len) }
538last_no_buffer_expansible_char_position_candidate = idx + 1
539continue
540}
541}
542continue
543} else if current_utf8_len == 3 {
544// runes like: ✔, ひらがな ...
545
546// Handle multi-byte characters byte-by-byte
547mut codepoint := u32(current_byte & ((1 << (7 - current_utf8_len)) - 1))
548for j in 1 .. current_utf8_len {
549if idx + j >= s.len {
550// Incomplete UTF-8 sequence, TODO handle error
551idx++
552continue
553}
554
555mut b := s[idx + j]
556if (b & 0xC0) != 0x80 {
557// Invalid continuation byte, TODO handle error
558idx++
559continue
560}
561
562codepoint = u32((codepoint << 6) | (b & 0x3F))
563}
564// runes like: ✔, ひらがな ...
565unsafe { buf.push_many(&null_unicode[0], null_unicode.len) }
566buf[buf.len - 1] = hex_digit(codepoint & 0xF)
567buf[buf.len - 2] = hex_digit((codepoint >> 4) & 0xF)
568buf[buf.len - 3] = hex_digit((codepoint >> 8) & 0xF)
569buf[buf.len - 4] = hex_digit((codepoint >> 12) & 0xF)
570idx += current_utf8_len - 1
571last_no_buffer_expansible_char_position_candidate = idx + 1
572}
573}
574
575buf << quote_rune
576}
577
578fn hex_digit(n int) u8 {
579if n < 10 {
580return `0` + n
581}
582return `a` + (n - 10)
583}
584