v/vlib/x/json2/encode.v
Larsimusrex bae7684276
Some checks failed
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
json decoder benchmark CI / json-encode-benchmark (push) Has been cancelled
json encoder benchmark CI / json-encode-benchmark (push) Has been cancelled
json2: replace encoder with new implementation (#25224)
2025-09-09 18:50:22 +03:00

490 lines
11 KiB
V

module json2
// EncoderOptions provides a list of options for encoding
@[params]
pub struct EncoderOptions {
pub:
prettify bool
indent_string string = ' '
newline_string string = '\n'
enum_as_int bool
escape_unicode bool
}
struct Encoder {
EncoderOptions
mut:
level int
prefix string
output []u8 = []u8{cap: 2048}
}
@[inline]
fn workaround_cast[T](val voidptr) T {
return *(&T(val))
}
// encode is a generic function that encodes a type into a JSON string.
pub fn encode[T](val T, config EncoderOptions) string {
mut encoder := Encoder{
EncoderOptions: config
}
encoder.encode_value(val)
return encoder.output.bytestr()
}
fn (mut encoder Encoder) encode_value[T](val T) {
$if T.unaliased_typ is string {
encoder.encode_string(workaround_cast[string](&val))
} $else $if T.unaliased_typ is bool {
encoder.encode_boolean(workaround_cast[bool](&val))
} $else $if T.unaliased_typ is u8 {
encoder.encode_number(workaround_cast[u8](&val))
} $else $if T.unaliased_typ is u16 {
encoder.encode_number(workaround_cast[u16](&val))
} $else $if T.unaliased_typ is u32 {
encoder.encode_number(workaround_cast[u32](&val))
} $else $if T.unaliased_typ is u64 {
encoder.encode_number(workaround_cast[u64](&val))
} $else $if T.unaliased_typ is i8 {
encoder.encode_number(workaround_cast[i8](&val))
} $else $if T.unaliased_typ is i16 {
encoder.encode_number(workaround_cast[i16](&val))
} $else $if T.unaliased_typ is int || T.unaliased_typ is i32 {
encoder.encode_number(workaround_cast[int](&val))
} $else $if T.unaliased_typ is i64 {
encoder.encode_number(workaround_cast[i64](&val))
} $else $if T.unaliased_typ is usize {
encoder.encode_number(workaround_cast[usize](&val))
} $else $if T.unaliased_typ is isize {
encoder.encode_number(workaround_cast[isize](&val))
} $else $if T.unaliased_typ is f32 {
encoder.encode_number(workaround_cast[f32](&val))
} $else $if T.unaliased_typ is f64 {
encoder.encode_number(workaround_cast[f64](&val))
} $else $if T.unaliased_typ is $array {
encoder.encode_array(val)
} $else $if T.unaliased_typ is $map {
encoder.encode_map(val)
} $else $if T.unaliased_typ is $enum {
encoder.encode_enum(val)
} $else $if T.unaliased_typ is $sumtype {
encoder.encode_sumtype(val)
} $else $if T is JsonEncoder { // uses T, because alias could be implementing JsonEncoder, while the base type does not
encoder.encode_custom(val)
} $else $if T is Encodable { // uses T, because alias could be implementing JsonEncoder, while the base type does not
encoder.encode_custom2(val)
} $else $if T.unaliased_typ is $struct {
unsafe { encoder.encode_struct(val) }
}
}
fn (mut encoder Encoder) encode_string(val string) {
encoder.output << `"`
mut buffer_start := 0
mut buffer_end := 0
for buffer_end < val.len {
character := val[buffer_end]
match character {
`"`, `\\` {
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
buffer_end++
buffer_start = buffer_end
encoder.output << `\\`
encoder.output << character
}
`\b` {
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
buffer_end++
buffer_start = buffer_end
encoder.output << `\\`
encoder.output << `b`
}
`\n` {
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
buffer_end++
buffer_start = buffer_end
encoder.output << `\\`
encoder.output << `n`
}
`\f` {
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
buffer_end++
buffer_start = buffer_end
encoder.output << `\\`
encoder.output << `f`
}
`\t` {
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
buffer_end++
buffer_start = buffer_end
encoder.output << `\\`
encoder.output << `t`
}
`\r` {
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
buffer_end++
buffer_start = buffer_end
encoder.output << `\\`
encoder.output << `r`
}
else {
if character < 0x20 { // control characters
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
buffer_end++
buffer_start = buffer_end
encoder.output << `\\`
encoder.output << `u`
hex_string := '${character:04x}'
unsafe { encoder.output.push_many(hex_string.str, 4) }
continue
}
if encoder.escape_unicode {
if character >= 0b1111_0000 { // four bytes
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
unicode_point_low := val[buffer_end..buffer_end + 4].bytes().byterune() or {
0
} - 0x10000
hex_string := '\\u${0xD800 + ((unicode_point_low >> 10) & 0x3FF):04X}\\u${
0xDC00 + (unicode_point_low & 0x3FF):04x}'
buffer_end += 4
buffer_start = buffer_end
unsafe { encoder.output.push_many(hex_string.str, 12) }
continue
} else if character >= 0b1110_0000 { // three bytes
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
hex_string := '\\u${val[buffer_end..buffer_end + 3].bytes().byterune() or {
0
}:04x}'
buffer_end += 3
buffer_start = buffer_end
unsafe { encoder.output.push_many(hex_string.str, 6) }
continue
} else if character >= 0b1100_0000 { // two bytes
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
hex_string := '\\u${val[buffer_end..buffer_end + 2].bytes().byterune() or {
0
}:04x}'
buffer_end += 2
buffer_start = buffer_end
unsafe { encoder.output.push_many(hex_string.str, 6) }
continue
}
}
buffer_end++
}
}
}
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
encoder.output << `"`
}
fn (mut encoder Encoder) encode_boolean(val bool) {
if val {
unsafe { encoder.output.push_many(true_string.str, true_string.len) }
} else {
unsafe { encoder.output.push_many(false_string.str, false_string.len) }
}
}
fn (mut encoder Encoder) encode_number[T](val T) {
integer_val := val.str()
$if T is $float {
if integer_val[integer_val.len - 1] == `0` { // ends in .0
unsafe {
integer_val.len -= 2
}
}
}
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
}
fn (mut encoder Encoder) encode_null() {
unsafe { encoder.output.push_many(null_string.str, null_string.len) }
}
fn (mut encoder Encoder) encode_array[T](val []T) {
encoder.output << `[`
if encoder.prettify {
encoder.increment_level()
encoder.add_indent()
}
for i, item in val {
encoder.encode_value(item)
if i < val.len - 1 {
encoder.output << `,`
if encoder.prettify {
encoder.add_indent()
}
} else {
if encoder.prettify {
encoder.decrement_level()
encoder.add_indent()
}
}
}
encoder.output << `]`
}
fn (mut encoder Encoder) encode_map[T](val map[string]T) {
encoder.output << `{`
if encoder.prettify {
encoder.increment_level()
encoder.add_indent()
}
mut i := 0
for key, value in val {
encoder.encode_string(key)
encoder.output << `:`
if encoder.prettify {
encoder.output << ` `
}
encoder.encode_value(value)
if i < val.len - 1 {
encoder.output << `,`
if encoder.prettify {
encoder.add_indent()
}
} else {
if encoder.prettify {
encoder.decrement_level()
encoder.add_indent()
}
}
i++
}
encoder.output << `}`
}
fn (mut encoder Encoder) encode_enum[T](val T) {
if encoder.enum_as_int {
encoder.encode_number(workaround_cast[int](&val))
} else {
mut enum_val := val.str()
$if val is $alias {
mut i := enum_val.len - 3
for enum_val[i] != `(` {
i--
}
enum_val = enum_val[i + 1..enum_val.len - 1]
}
encoder.output << `"`
unsafe { encoder.output.push_many(enum_val.str, enum_val.len) }
encoder.output << `"`
}
}
fn (mut encoder Encoder) encode_sumtype[T](val T) {
$for variant in T.variants {
if val is variant {
encoder.encode_value(val)
}
}
}
struct EncoderFieldInfo {
key_name string
is_skip bool
is_omitempty bool
is_required bool
}
fn check_not_empty[T](val T) bool {
$if val is string {
if val == '' {
return false
}
} $else $if val is $int || val is $float {
if val == 0 {
return false
}
}
return true
}
@[unsafe]
fn (mut encoder Encoder) encode_struct[T](val T) {
encoder.output << `{`
static field_infos := &[]EncoderFieldInfo(nil)
if field_infos == nil {
field_infos = &[]EncoderFieldInfo{}
$for field in T.fields {
mut is_skip := false
mut key_name := ''
mut is_omitempty := false
mut is_required := false
for attr in field.attrs {
match attr {
'skip' {
is_skip = true
break
}
'omitempty' {
is_omitempty = true
}
'required' {
is_required = true
}
else {}
}
if attr.starts_with('json:') {
if attr == 'json: -' {
is_skip = true
break
}
key_name = attr[6..]
}
}
field_infos << EncoderFieldInfo{
key_name: if key_name == '' { field.name } else { key_name }
is_skip: is_skip
is_omitempty: is_omitempty
is_required: is_required
}
}
}
mut i := 0
mut is_first := true
$for field in T.fields {
field_info := field_infos[i]
i++
mut write_field := true
if field_info.is_skip {
write_field = false
} else {
value := val.$(field.name)
if field_info.is_omitempty {
$if value is $option {
write_field = check_not_empty(value)
} $else {
write_field = check_not_empty(value)
}
}
if !field_info.is_required {
$if value is $option {
if value == none {
write_field = false
}
}
}
}
$if field.indirections != 0 {
if val.$(field.name) == unsafe { nil } {
write_field = false
}
}
if write_field {
if is_first {
if encoder.prettify {
encoder.increment_level()
}
is_first = false
} else {
encoder.output << `,`
}
if encoder.prettify {
encoder.add_indent()
}
encoder.encode_string(field_info.key_name)
encoder.output << `:`
if encoder.prettify {
encoder.output << ` `
}
$if field is $option {
if val.$(field.name) == none {
unsafe { encoder.output.push_many(null_string.str, null_string.len) }
} else {
encoder.encode_value(val.$(field.name))
}
} $else $if field.indirections == 1 {
encoder.encode_value(*val.$(field.name))
} $else $if field.indirections == 2 {
encoder.encode_value(**val.$(field.name))
} $else $if field.indirections == 3 {
encoder.encode_value(***val.$(field.name))
} $else {
encoder.encode_value(val.$(field.name))
}
}
}
if encoder.prettify && !is_first {
encoder.decrement_level()
encoder.add_indent()
}
encoder.output << `}`
}
fn (mut encoder Encoder) encode_custom[T](val T) {
integer_val := val.to_json()
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
}
fn (mut encoder Encoder) encode_custom2[T](val T) {
integer_val := val.json_str()
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
}
fn (mut encoder Encoder) increment_level() {
encoder.level++
encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level)
}
fn (mut encoder Encoder) decrement_level() {
encoder.level--
encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level)
}
fn (mut encoder Encoder) add_indent() {
unsafe { encoder.output.push_many(encoder.prefix.str, encoder.prefix.len) }
}