Skip to content

Commit

Permalink
x.json2.decode2: minor improvements and bugfixes (#23083)
Browse files Browse the repository at this point in the history
  • Loading branch information
enghitalo authored Dec 7, 2024
1 parent de3b184 commit e32e9f7
Show file tree
Hide file tree
Showing 12 changed files with 1,329 additions and 81 deletions.
104 changes: 44 additions & 60 deletions vlib/x/json2/decoder2/decode.v
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const false_in_string = 'false'

const float_zero_in_string = '0.0'

const whitespace_chars = [` `, `\t`, `\n`]!

// Node represents a node in a linked list to store ValueInfo.
struct Node[T] {
mut:
Expand Down Expand Up @@ -147,55 +149,6 @@ pub enum ValueKind {
null
}

// check_if_json_match checks if the JSON string matches the expected type T.
fn check_if_json_match[T](val string) ! {
// check if the JSON string is empty
if val == '' {
return error('empty string')
}

// check if generic type matches the JSON type
value_kind := get_value_kind(val[0])

$if T is $option {
// TODO
} $else $if T is $sumtype {
// TODO
} $else $if T is $alias {
// TODO
} $else $if T is $string {
if value_kind != .string_ {
return error('Expected string, but got ${value_kind}')
}
} $else $if T is time.Time {
if value_kind != .string_ {
return error('Expected string, but got ${value_kind}')
}
} $else $if T is $map {
if value_kind != .object {
return error('Expected object, but got ${value_kind}')
}
} $else $if T is $array {
if value_kind != .array {
return error('Expected array, but got ${value_kind}')
}
} $else $if T is $struct {
if value_kind != .object {
return error('Expected object, but got ${value_kind}')
}
} $else $if T in [$enum, $int, $float] {
if value_kind != .number {
return error('Expected number, but got ${value_kind}')
}
} $else $if T is bool {
if value_kind != .boolean {
return error('Expected boolean, but got ${value_kind}')
}
} $else {
return error('cannot decode value with ${value_kind} type')
}
}

// error generates an error message with context from the JSON string.
fn (mut checker Decoder) error(message string) ! {
json := if checker.json.len < checker.checker_idx + 5 {
Expand Down Expand Up @@ -234,6 +187,14 @@ fn (mut checker Decoder) check_json_format(val string) ! {
return checker.error('empty string')
}

// skip whitespace
for val[checker.checker_idx] in whitespace_chars {
if checker.checker_idx >= checker_end - 1 {
break
}
checker.checker_idx++
}

// check if generic type matches the JSON type
value_kind := get_value_kind(val[checker.checker_idx])
start_idx_position := checker.checker_idx
Expand Down Expand Up @@ -264,6 +225,9 @@ fn (mut checker Decoder) check_json_format(val string) ! {
checker.checker_idx += 3
}
.object {
if checker_end - checker.checker_idx < 2 {
return checker.error('EOF error: expecting a complete object after `{`')
}
checker.checker_idx++
for val[checker.checker_idx] != `}` {
// check if the JSON string is an empty object
Expand All @@ -272,7 +236,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
}

// skip whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
if checker.checker_idx >= checker_end - 1 {
break
}
Expand All @@ -294,7 +258,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
if checker.checker_idx >= checker_end - 1 {
return checker.error('EOF error: key colon not found')
}
if val[checker.checker_idx] !in [` `, `\t`, `\n`] {
if val[checker.checker_idx] !in whitespace_chars {
return checker.error('invalid value after object key')
}
checker.checker_idx++
Expand All @@ -307,15 +271,15 @@ fn (mut checker Decoder) check_json_format(val string) ! {
checker.checker_idx++

// skip whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}

match val[checker.checker_idx] {
`"`, `[`, `{`, `0`...`9`, `-`, `n`, `t`, `f` {
checker.check_json_format(val)!
// whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] == `}` {
Expand All @@ -327,11 +291,11 @@ fn (mut checker Decoder) check_json_format(val string) ! {

if val[checker.checker_idx] == `,` {
checker.checker_idx++
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] != `"` {
return checker.error('Expecting object key')
return checker.error('Expecting object key after `,`')
}
} else {
if val[checker.checker_idx] == `}` {
Expand Down Expand Up @@ -360,7 +324,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {

for val[checker.checker_idx] != `]` {
// skip whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
if checker.checker_idx >= checker_end - 1 {
break
}
Expand All @@ -378,7 +342,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
checker.check_json_format(val)!

// whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] == `]` {
Expand All @@ -390,7 +354,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {

if val[checker.checker_idx] == `,` {
checker.checker_idx++
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] == `]` {
Expand Down Expand Up @@ -548,7 +512,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {

for checker.checker_idx < checker_end - 1 && val[checker.checker_idx] !in [`,`, `:`, `}`, `]`] {
// get trash characters after the value
if val[checker.checker_idx] !in [` `, `\t`, `\n`] {
if val[checker.checker_idx] !in whitespace_chars {
checker.error('invalid value. Unexpected character after ${value_kind} end')!
} else {
// whitespace
Expand All @@ -558,17 +522,23 @@ fn (mut checker Decoder) check_json_format(val string) ! {
}

// decode decodes a JSON string into a specified type.
@[manualfree]
pub fn decode[T](val string) !T {
if val == '' {
return error('empty string')
}
mut decoder := Decoder{
json: val
}

decoder.check_json_format(val)!
check_if_json_match[T](val)!

mut result := T{}
decoder.current_node = decoder.values_info.head
decoder.decode_value(mut &result)!
unsafe {
decoder.values_info.free()
}
return result
}

Expand Down Expand Up @@ -629,6 +599,8 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
}

val = string_buffer.bytestr()
} else {
return error('Expected string, but got ${string_info.value_kind}')
}
} $else $if T.unaliased_typ is $sumtype {
decoder.decode_sumtype(mut val)!
Expand Down Expand Up @@ -852,6 +824,8 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
}
current_field_info = current_field_info.next
}
} else {
return error('Expected object, but got ${struct_info.value_kind}')
}
unsafe {
struct_fields_info.free()
Expand All @@ -860,6 +834,10 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
} $else $if T.unaliased_typ is bool {
value_info := decoder.current_node.value

if value_info.value_kind != .boolean {
return error('Expected boolean, but got ${value_info.value_kind}')
}

unsafe {
val = vmemcmp(decoder.json.str + value_info.position, true_in_string.str,
true_in_string.len) == 0
Expand All @@ -873,6 +851,8 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
unsafe {
string_buffer_to_generic_number(val, bytes)
}
} else {
return error('Expected number, but got ${value_info.value_kind}')
}
} $else {
return error('cannot decode value with ${typeof(val).name} type')
Expand Down Expand Up @@ -904,6 +884,8 @@ fn (mut decoder Decoder) decode_array[T](mut val []T) ! {

val << array_element
}
} else {
return error('Expected array, but got ${array_info.value_kind}')
}
}

Expand Down Expand Up @@ -946,6 +928,8 @@ fn (mut decoder Decoder) decode_map[K, V](mut val map[K]V) ! {
}
decoder.decode_value(mut val[key])!
}
} else {
return error('Expected object, but got ${map_info.value_kind}')
}
}

Expand Down
7 changes: 2 additions & 5 deletions vlib/x/json2/decoder2/decode_sumtype.v
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T
$if initialized_sumtype is $sumtype {
$for v in initialized_sumtype.variants {
if initialized_sumtype is v {
$if v is $array {
mut val := initialized_sumtype.clone()
decoder.decode_value(mut val)!
return T(val)
} $else {
// workaround for auto generated function considering sumtype as array
unsafe {
mut val := initialized_sumtype
decoder.decode_value(mut val)!
return T(val)
Expand Down
28 changes: 14 additions & 14 deletions vlib/x/json2/decoder2/decode_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,62 @@ fn test_check_if_json_match() {
// /* Test wrong string values */
mut has_error := false

check_if_json_match[string]('{"key": "value"}') or {
decode[string]('{"key": "value"}') or {
assert err.str() == 'Expected string, but got object'
has_error = true
}
assert has_error, 'Expected error'
has_error = false

check_if_json_match[map[string]string]('"value"') or {
decode[map[string]string]('"value"') or {
assert err.str() == 'Expected object, but got string_'
has_error = true
}
assert has_error, 'Expected error'
has_error = false

check_if_json_match[[]int]('{"key": "value"}') or {
decode[[]int]('{"key": "value"}') or {
assert err.str() == 'Expected array, but got object'
has_error = true
}
assert has_error, 'Expected error'
has_error = false

check_if_json_match[string]('[1, 2, 3]') or {
decode[string]('[1, 2, 3]') or {
assert err.str() == 'Expected string, but got array'
has_error = true
}
assert has_error, 'Expected error'
has_error = false

check_if_json_match[int]('{"key": "value"}') or {
decode[int]('{"key": "value"}') or {
assert err.str() == 'Expected number, but got object'
has_error = true
}
assert has_error, 'Expected error'
has_error = false

check_if_json_match[bool]('{"key": "value"}') or {
decode[bool]('{"key": "value"}') or {
assert err.str() == 'Expected boolean, but got object'
has_error = true
}
assert has_error, 'Expected error'
has_error = false

// /* Right string values */
check_if_json_match[string]('"value"') or { assert false }
decode[string]('"value"') or { assert false }

check_if_json_match[map[string]string]('{"key": "value"}') or { assert false }
decode[map[string]string]('{"key": "value"}') or { assert false }

check_if_json_match[[]int]('[1, 2, 3]') or { assert false }
decode[[]int]('[1, 2, 3]') or { assert false }

check_if_json_match[string]('"string"') or { assert false }
decode[string]('"string"') or { assert false }

check_if_json_match[int]('123') or { assert false }
decode[int]('123') or { assert false }

check_if_json_match[bool]('true') or { assert false }
decode[bool]('true') or { assert false }

check_if_json_match[bool]('false') or { assert false }
decode[bool]('false') or { assert false }

// TODO: test null
}
Expand Down Expand Up @@ -157,7 +157,7 @@ fn test_check_json_format() {
},
{
'json': '{"key": 123, "key2": 456,}'
'error': '\n{"key": 123, "key2": 456,}\n ^ Expecting object key'
'error': '\n{"key": 123, "key2": 456,}\n ^ Expecting object key after `,`'
},
{
'json': '[[1, 2, 3], [4, 5, 6],]'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import x.json2.decoder2 as json
import x.json2

struct AnyStruct[T] {
val T
}

struct OptAnyStruct[T] {
val ?T
}

// struct OptAnyArrStruct {
// val []?json2.Any
// }

fn test_values() {
assert json.decode[AnyStruct[json2.Any]]('{"val":5}')!.val.int() == 5
assert json.decode[OptAnyStruct[json2.Any]]('{}')!.val == none
assert json.decode[AnyStruct[[]json2.Any]]('{"val":[5,10]}')!.val.map(it.int()) == [
5,
10,
]
// assert json.decode[OptAnyArrStruct]('{"val":[5,null,10]}')!.val == [?json2.Any(5),json.Null{},10] // skipped because test still fails even though they're the same

assert json2.encode[AnyStruct[json2.Any]](AnyStruct[json2.Any]{json2.Any(5)}) == '{"val":5}'
assert json2.encode[OptAnyStruct[json2.Any]](OptAnyStruct[json2.Any]{none}) == '{}'
assert json2.encode[AnyStruct[[]json2.Any]](AnyStruct[[]json2.Any]{[json2.Any(5), 10]}) == '{"val":[5,10]}'
// assert json2.encode[OptAnyArrStruct](OptAnyArrStruct{[?json2.Any(5),none,10]}) == '{"val":[5,null,10]}' // encode_array has not implemented optional arrays yet
}
Loading

0 comments on commit e32e9f7

Please sign in to comment.