diff --git a/vlib/x/json2/decoder2/check.v b/vlib/x/json2/decoder2/check.v new file mode 100644 index 0000000000..81be1fe7bf --- /dev/null +++ b/vlib/x/json2/decoder2/check.v @@ -0,0 +1,444 @@ +module decoder2 + +// check_json_format checks if the JSON string is valid and updates the decoder state. +fn (mut checker Decoder) check_json_format() ! { + // skip whitespace + for checker.json[checker.checker_idx] in whitespace_chars { + if checker.checker_idx == checker.json.len { + break + } + checker.checker_idx++ + } + + start_idx_position := checker.checker_idx + + mut actual_value_info_pointer := unsafe { &ValueInfo(nil) } + + match checker.json[checker.checker_idx] { + `"` { + checker.values_info.push(ValueInfo{ + position: checker.checker_idx + value_kind: .string_ + }) + + actual_value_info_pointer = checker.values_info.last() + + checker.check_string()! + } + `-`, `0`...`9` { + checker.values_info.push(ValueInfo{ + position: checker.checker_idx + value_kind: .number + }) + + actual_value_info_pointer = checker.values_info.last() + + checker.check_number()! + } + `t`, `f` { + checker.values_info.push(ValueInfo{ + position: checker.checker_idx + value_kind: .boolean + }) + + actual_value_info_pointer = checker.values_info.last() + + checker.check_boolean()! + } + `n` { + checker.values_info.push(ValueInfo{ + position: checker.checker_idx + value_kind: .null + }) + + actual_value_info_pointer = checker.values_info.last() + + checker.check_null()! + } + `[` { + checker.values_info.push(ValueInfo{ + position: checker.checker_idx + value_kind: .array + }) + + actual_value_info_pointer = checker.values_info.last() + + checker.check_array()! + } + `{` { + checker.values_info.push(ValueInfo{ + position: checker.checker_idx + value_kind: .object + }) + + actual_value_info_pointer = checker.values_info.last() + + checker.check_object()! + } + else { + checker.checker_error('unknown value kind')! + } + } + + actual_value_info_pointer.length = checker.checker_idx + 1 - start_idx_position + + if checker.checker_idx < checker.json.len { + checker.checker_idx++ + } + + for checker.checker_idx < checker.json.len + && checker.json[checker.checker_idx] !in [`,`, `:`, `}`, `]`] { + // get trash characters after the value + if checker.json[checker.checker_idx] !in whitespace_chars { + checker.checker_error('invalid value. Unexpected character after ${actual_value_info_pointer.value_kind} end')! + } else { + // whitespace + } + checker.checker_idx++ + } +} + +fn (mut checker Decoder) check_string() ! { + // check if the JSON string is a valid string + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('EOF error: string not closed') + } + + checker.checker_idx++ + + // check if the JSON string is a valid escape sequence + for checker.json[checker.checker_idx] != `"` { + if checker.json[checker.checker_idx] == `\\` { + if checker.checker_idx + 1 >= checker.json.len - 1 { + return checker.checker_error('invalid escape sequence') + } + escaped_char := checker.json[checker.checker_idx + 1] + match escaped_char { + `/`, `b`, `f`, `n`, `r`, `t`, `"`, `\\` { + checker.checker_idx++ // make sure escaped quotation marks are skipped + } + `u` { + // check if the JSON string is a valid unicode escape sequence + escaped_char_last_index := checker.checker_idx + 5 + + if escaped_char_last_index < checker.json.len { + // 2 bytes for the unicode escape sequence `\u` + checker.checker_idx += 2 + + for checker.checker_idx < escaped_char_last_index { + match checker.json[checker.checker_idx] { + `0`...`9`, `a`...`f`, `A`...`F` { + checker.checker_idx++ + } + else { + return checker.checker_error('invalid unicode escape sequence') + } + } + } + continue + } else { + return checker.checker_error('short unicode escape sequence ${checker.json[checker.checker_idx..escaped_char_last_index]}') + } + } + else { + return checker.checker_error('unknown escape sequence') + } + } + } + checker.checker_idx++ + } +} + +fn (mut checker Decoder) check_number() ! { + // check if the JSON string is a valid float or integer + if checker.json[checker.checker_idx] == `-` { + checker.checker_idx++ + } + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('expected digit got EOF') + } + + // integer part + if checker.json[checker.checker_idx] == `0` { + checker.checker_idx++ + } else if checker.json[checker.checker_idx] >= `1` && checker.json[checker.checker_idx] <= `9` { + checker.checker_idx++ + + for checker.checker_idx < checker.json.len && checker.json[checker.checker_idx] >= `0` + && checker.json[checker.checker_idx] <= `9` { + checker.checker_idx++ + } + } else { + return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}') + } + + // fraction part + if checker.checker_idx != checker.json.len && checker.json[checker.checker_idx] == `.` { + checker.checker_idx++ + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('expected digit got EOF') + } + + if checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` { + for checker.checker_idx < checker.json.len && checker.json[checker.checker_idx] >= `0` + && checker.json[checker.checker_idx] <= `9` { + checker.checker_idx++ + } + } else { + return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}') + } + } + + // exponent part + if checker.checker_idx != checker.json.len + && (checker.json[checker.checker_idx] == `e` || checker.json[checker.checker_idx] == `E`) { + checker.checker_idx++ + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('expected digit got EOF') + } + + if checker.json[checker.checker_idx] == `-` || checker.json[checker.checker_idx] == `+` { + checker.checker_idx++ + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('expected digit got EOF') + } + } + + if checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` { + for checker.checker_idx < checker.json.len && checker.json[checker.checker_idx] >= `0` + && checker.json[checker.checker_idx] <= `9` { + checker.checker_idx++ + } + } else { + return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}') + } + } + + checker.checker_idx-- +} + +fn (mut checker Decoder) check_boolean() ! { + // check if the JSON string is a valid boolean + match checker.json[checker.checker_idx] { + `t` { + if checker.json.len - checker.checker_idx <= 3 { + return checker.checker_error('EOF error: expecting `true`') + } + + is_not_ok := unsafe { + vmemcmp(checker.json.str + checker.checker_idx, true_in_string.str, true_in_string.len) + } + + if is_not_ok != 0 { + return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx.. + checker.checker_idx + 4]}` instead of `true`') + } + checker.checker_idx += 3 + } + `f` { + if checker.json.len - checker.checker_idx <= 4 { + return checker.checker_error('EOF error: expecting `false`') + } + + is_not_ok := unsafe { + vmemcmp(checker.json.str + checker.checker_idx, false_in_string.str, false_in_string.len) + } + + if is_not_ok != 0 { + return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx.. + checker.checker_idx + 5]}` instead of `false`') + } + + checker.checker_idx += 4 + } + else { + return checker.checker_error('invalid boolean') + } + } +} + +fn (mut checker Decoder) check_null() ! { + // check if the JSON string is a null value + if checker.json.len - checker.checker_idx <= 3 { + return checker.checker_error('EOF error: expecting `null`') + } + + is_not_ok := unsafe { + vmemcmp(checker.json.str + checker.checker_idx, null_in_string.str, null_in_string.len) + } + + if is_not_ok != 0 { + return checker.checker_error('invalid null value. Got `${checker.json[checker.checker_idx.. + checker.checker_idx + 4]}` instead of `null`') + } + checker.checker_idx += 3 +} + +fn (mut checker Decoder) check_array() ! { + // check if the JSON string is an empty array + if checker.json.len >= checker.checker_idx + 2 { + checker.checker_idx++ + } else { + return checker.checker_error('EOF error: There are not enough length for an array') + } + + for checker.json[checker.checker_idx] != `]` { + // skip whitespace + for checker.json[checker.checker_idx] in whitespace_chars { + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + break + } + checker.checker_idx++ + } + + if checker.json[checker.checker_idx] == `]` { + break + } + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('EOF error: array not closed') + } + + checker.check_json_format()! + + // whitespace + for checker.json[checker.checker_idx] in whitespace_chars { + checker.checker_idx++ + } + if checker.json[checker.checker_idx] == `]` { + break + } + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('EOF error: braces are not closed') + } + + if checker.json[checker.checker_idx] == `,` { + checker.checker_idx++ + for checker.json[checker.checker_idx] in whitespace_chars { + checker.checker_idx++ + } + if checker.json[checker.checker_idx] == `]` { + return checker.checker_error('Cannot use `,`, before `]`') + } + continue + } else { + if checker.json[checker.checker_idx] == `]` { + break + } else { + return checker.checker_error('`]` after value') + } + } + } +} + +fn (mut checker Decoder) check_object() ! { + if checker.json.len - checker.checker_idx < 2 { + return checker.checker_error('EOF error: expecting a complete object after `{`') + } + checker.checker_idx++ + for checker.json[checker.checker_idx] != `}` { + // skip whitespace + for checker.json[checker.checker_idx] in whitespace_chars { + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + break + } + checker.checker_idx++ + } + + if checker.json[checker.checker_idx] == `}` { + continue + } + + if checker.json[checker.checker_idx] != `"` { + return checker.checker_error('Expecting object key') + } + + // Object key + checker.check_json_format()! + + for checker.json[checker.checker_idx] != `:` { + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('EOF error: key colon not found') + } + if checker.json[checker.checker_idx] !in whitespace_chars { + return checker.checker_error('invalid value after object key') + } + checker.checker_idx++ + } + + if checker.json[checker.checker_idx] != `:` { + return checker.checker_error('Expecting `:` after object key') + } + + // skip `:` + checker.checker_idx++ + + // skip whitespace + for checker.json[checker.checker_idx] in whitespace_chars { + checker.checker_idx++ + } + + match checker.json[checker.checker_idx] { + `"`, `[`, `{`, `0`...`9`, `-`, `n`, `t`, `f` { + checker.check_json_format()! + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('EOF error: braces are not closed') + } + + // whitespace + for checker.json[checker.checker_idx] in whitespace_chars { + checker.checker_idx++ + } + if checker.json[checker.checker_idx] == `}` { + break + } + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('EOF error: braces are not closed') + } + + if checker.json[checker.checker_idx] == `,` { + checker.checker_idx++ + + if checker.checker_idx == checker.json.len { + checker.checker_idx-- + return checker.checker_error('EOF error: Expecting object key after `,`') + } + + for checker.json[checker.checker_idx] in whitespace_chars { + checker.checker_idx++ + } + if checker.json[checker.checker_idx] != `"` { + return checker.checker_error('Expecting object key after `,`') + } + } else { + if checker.json[checker.checker_idx] == `}` { + break + } else { + return checker.checker_error('invalid object value') + } + } + } + else { + return checker.checker_error('invalid object value') + } + } + } +} diff --git a/vlib/x/json2/decoder2/decode.v b/vlib/x/json2/decoder2/decode.v index 497b4c69f7..847187d107 100644 --- a/vlib/x/json2/decoder2/decode.v +++ b/vlib/x/json2/decoder2/decode.v @@ -22,8 +22,7 @@ mut: // ValueInfo represents the position and length of a value, such as string, number, array, object key, and object value in a JSON string. struct ValueInfo { - position int // The position of the value in the JSON string. -pub: + position int // The position of the value in the JSON string. value_kind ValueKind // The kind of the value. mut: length int // The length of the value in the JSON string. @@ -51,20 +50,6 @@ mut: current_node &Node[ValueInfo] = unsafe { nil } // The current node in the linked list. } -// new_decoder creates a new JSON decoder. -pub fn new_decoder[T](json string) !Decoder { - mut decoder := Decoder{ - json: json - } - - decoder.check_json_format(json)! - check_if_json_match[T](json)! - - decoder.current_node = decoder.values_info.head - - return decoder -} - // LinkedList represents a linked list to store ValueInfo. struct LinkedList[T] { mut: @@ -138,8 +123,7 @@ fn (list &LinkedList[T]) free() { } // ValueKind represents the kind of a JSON value. -pub enum ValueKind { - unknown +enum ValueKind { array object string_ @@ -148,8 +132,8 @@ pub enum ValueKind { null } -const max_context_lenght = 50 -const max_extra_charaters = 5 +const max_context_length = 50 +const max_extra_characters = 5 const tab_width = 8 pub struct JsonDecodeError { @@ -189,13 +173,13 @@ fn (mut checker Decoder) checker_error(message string) ! { } } - cutoff := character_number > max_context_lenght + cutoff := character_number > max_context_length // either start of string, last newline or a limited amount of characters - context_start := if cutoff { position - max_context_lenght } else { last_newline } + context_start := if cutoff { position - max_context_length } else { last_newline } // print some extra characters - mut context_end := int_min(checker.json.len, position + max_extra_charaters) + mut context_end := int_min(checker.json.len, position + max_extra_characters) context_end_newline := checker.json[position..context_end].index_u8(`\n`) if context_end_newline != -1 { @@ -213,7 +197,7 @@ fn (mut checker Decoder) checker_error(message string) ! { context += '\n' if cutoff { - context += ' '.repeat(max_context_lenght + 3) + context += ' '.repeat(max_context_length + 3) } else { context += ' '.repeat(character_number) } @@ -237,7 +221,7 @@ fn (mut decoder Decoder) decode_error(message string) ! { } start := error_info.position - end := start + int_min(error_info.length, max_context_lenght) + end := start + int_min(error_info.length, max_context_length) mut line_number := 0 mut character_number := 0 @@ -258,13 +242,13 @@ fn (mut decoder Decoder) decode_error(message string) ! { } } - cutoff := character_number > max_context_lenght + cutoff := character_number > max_context_length // either start of string, last newline or a limited amount of characters - context_start := if cutoff { start - max_context_lenght } else { last_newline } + context_start := if cutoff { start - max_context_length } else { last_newline } // print some extra characters - mut context_end := int_min(decoder.json.len, end + max_extra_charaters) + mut context_end := int_min(decoder.json.len, end + max_extra_characters) context_end_newline := decoder.json[end..context_end].index_u8(`\n`) if context_end_newline != -1 { @@ -282,7 +266,7 @@ fn (mut decoder Decoder) decode_error(message string) ! { context += '\n' if cutoff { - context += ' '.repeat(max_context_lenght + 3) + context += ' '.repeat(max_context_length + 3) } else { context += ' '.repeat(character_number) } @@ -296,413 +280,6 @@ fn (mut decoder Decoder) decode_error(message string) ! { } } -// check_json_format checks if the JSON string is valid and updates the decoder state. -fn (mut checker Decoder) check_json_format(val string) ! { - checker_end := checker.json.len - // check if the JSON string is empty - if val == '' { - return checker.checker_error('empty string') - } - - // skip whitespace - for val[checker.checker_idx] in whitespace_chars { - if checker.checker_idx == checker_end { - 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 - checker.values_info.push(ValueInfo{ - position: start_idx_position - value_kind: value_kind - }) - - mut actual_value_info_pointer := checker.values_info.last() - match value_kind { - .unknown { - return checker.checker_error('unknown value kind') - } - .null { - // check if the JSON string is a null value - if checker_end - checker.checker_idx <= 3 { - return checker.checker_error('EOF error: expecting `null`') - } - - is_not_ok := unsafe { - vmemcmp(checker.json.str + checker.checker_idx, null_in_string.str, null_in_string.len) - } - - if is_not_ok != 0 { - return checker.checker_error('invalid null value. Got `${checker.json[checker.checker_idx.. - checker.checker_idx + 4]}` instead of `null`') - } - checker.checker_idx += 3 - } - .object { - if checker_end - checker.checker_idx < 2 { - return checker.checker_error('EOF error: expecting a complete object after `{`') - } - checker.checker_idx++ - for val[checker.checker_idx] != `}` { - // skip whitespace - for val[checker.checker_idx] in whitespace_chars { - if checker.checker_idx == checker_end { - checker.checker_idx-- - break - } - checker.checker_idx++ - } - - if val[checker.checker_idx] == `}` { - continue - } - - if val[checker.checker_idx] != `"` { - return checker.checker_error('Expecting object key') - } - - // Object key - checker.check_json_format(val)! - - for val[checker.checker_idx] != `:` { - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('EOF error: key colon not found') - } - if val[checker.checker_idx] !in whitespace_chars { - return checker.checker_error('invalid value after object key') - } - checker.checker_idx++ - } - - if val[checker.checker_idx] != `:` { - return checker.checker_error('Expecting `:` after object key') - } - // skip `:` - checker.checker_idx++ - - // skip whitespace - 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)! - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('EOF error: braces are not closed') - } - - // whitespace - for val[checker.checker_idx] in whitespace_chars { - checker.checker_idx++ - } - if val[checker.checker_idx] == `}` { - break - } - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('EOF error: braces are not closed') - } - - if val[checker.checker_idx] == `,` { - checker.checker_idx++ - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('EOF error: Expecting object key after `,`') - } - - for val[checker.checker_idx] in whitespace_chars { - checker.checker_idx++ - } - if val[checker.checker_idx] != `"` { - return checker.checker_error('Expecting object key after `,`') - } - } else { - if val[checker.checker_idx] == `}` { - break - } else { - return checker.checker_error('invalid object value') - } - } - } - else { - return checker.checker_error('invalid object value') - } - } - } - } - .array { - // check if the JSON string is an empty array - if checker_end >= checker.checker_idx + 2 { - checker.checker_idx++ - } else { - return checker.checker_error('EOF error: There are not enough length for an array') - } - - for val[checker.checker_idx] != `]` { - // skip whitespace - for val[checker.checker_idx] in whitespace_chars { - if checker.checker_idx == checker_end { - checker.checker_idx-- - break - } - checker.checker_idx++ - } - - if val[checker.checker_idx] == `]` { - break - } - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('EOF error: array not closed') - } - - checker.check_json_format(val)! - - // whitespace - for val[checker.checker_idx] in whitespace_chars { - checker.checker_idx++ - } - if val[checker.checker_idx] == `]` { - break - } - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('EOF error: braces are not closed') - } - - if val[checker.checker_idx] == `,` { - checker.checker_idx++ - for val[checker.checker_idx] in whitespace_chars { - checker.checker_idx++ - } - if val[checker.checker_idx] == `]` { - return checker.checker_error('Cannot use `,`, before `]`') - } - continue - } else { - if val[checker.checker_idx] == `]` { - break - } else { - return checker.checker_error('`]` after value') - } - } - } - } - .string_ { - // check if the JSON string is a valid string - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('EOF error: string not closed') - } - - checker.checker_idx++ - - // check if the JSON string is a valid escape sequence - for val[checker.checker_idx] != `"` { - if val[checker.checker_idx] == `\\` { - if checker.checker_idx + 1 >= checker_end - 1 { - return checker.checker_error('invalid escape sequence') - } - escaped_char := val[checker.checker_idx + 1] - match escaped_char { - `/`, `b`, `f`, `n`, `r`, `t`, `"`, `\\` { - checker.checker_idx++ // make sure escaped quotation marks are skipped - } - `u` { - // check if the JSON string is a valid unicode escape sequence - escaped_char_last_index := checker.checker_idx + 5 - - if escaped_char_last_index < checker_end { - // 2 bytes for the unicode escape sequence `\u` - checker.checker_idx += 2 - - for checker.checker_idx < escaped_char_last_index { - match val[checker.checker_idx] { - `0`...`9`, `a`...`f`, `A`...`F` { - checker.checker_idx++ - } - else { - return checker.checker_error('invalid unicode escape sequence') - } - } - } - continue - } else { - return checker.checker_error('short unicode escape sequence ${checker.json[checker.checker_idx..escaped_char_last_index]}') - } - } - else { - return checker.checker_error('unknown escape sequence') - } - } - } - checker.checker_idx++ - } - } - .number { - // check if the JSON string is a valid float or integer - if val[checker.checker_idx] == `-` { - checker.checker_idx++ - } - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('expected digit got EOF') - } - - // integer part - if val[checker.checker_idx] == `0` { - checker.checker_idx++ - } else if val[checker.checker_idx] >= `1` && val[checker.checker_idx] <= `9` { - checker.checker_idx++ - - for checker.checker_idx < checker_end && val[checker.checker_idx] >= `0` - && val[checker.checker_idx] <= `9` { - checker.checker_idx++ - } - } else { - return checker.checker_error('expected digit got ${val[checker.checker_idx].ascii_str()}') - } - - // fraction part - if checker.checker_idx != checker_end && val[checker.checker_idx] == `.` { - checker.checker_idx++ - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('expected digit got EOF') - } - - if val[checker.checker_idx] >= `0` && val[checker.checker_idx] <= `9` { - for checker.checker_idx < checker_end && val[checker.checker_idx] >= `0` - && val[checker.checker_idx] <= `9` { - checker.checker_idx++ - } - } else { - return checker.checker_error('expected digit got ${val[checker.checker_idx].ascii_str()}') - } - } - - // exponent part - if checker.checker_idx != checker_end - && (val[checker.checker_idx] == `e` || val[checker.checker_idx] == `E`) { - checker.checker_idx++ - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('expected digit got EOF') - } - - if val[checker.checker_idx] == `-` || val[checker.checker_idx] == `+` { - checker.checker_idx++ - - if checker.checker_idx == checker_end { - checker.checker_idx-- - return checker.checker_error('expected digit got EOF') - } - } - - if val[checker.checker_idx] >= `0` && val[checker.checker_idx] <= `9` { - for checker.checker_idx < checker_end && val[checker.checker_idx] >= `0` - && val[checker.checker_idx] <= `9` { - checker.checker_idx++ - } - } else { - return checker.checker_error('expected digit got ${val[checker.checker_idx].ascii_str()}') - } - } - - checker.checker_idx-- - } - .boolean { - // check if the JSON string is a valid boolean - match val[checker.checker_idx] { - `t` { - if checker_end - checker.checker_idx <= 3 { - return checker.checker_error('EOF error: expecting `true`') - } - - is_not_ok := unsafe { - vmemcmp(checker.json.str + checker.checker_idx, true_in_string.str, - true_in_string.len) - } - - if is_not_ok != 0 { - return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx.. - checker.checker_idx + 4]}` instead of `true`') - } - checker.checker_idx += 3 - } - `f` { - if checker_end - checker.checker_idx <= 4 { - return checker.checker_error('EOF error: expecting `false`') - } - - is_not_ok := unsafe { - vmemcmp(checker.json.str + checker.checker_idx, false_in_string.str, - false_in_string.len) - } - - if is_not_ok != 0 { - return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx.. - checker.checker_idx + 5]}` instead of `false`') - } - - checker.checker_idx += 4 - } - else { - return checker.checker_error('invalid boolean') - } - } - } - } - - actual_value_info_pointer.length = checker.checker_idx + 1 - start_idx_position - - if checker.checker_idx < checker_end { - checker.checker_idx++ - } - - for checker.checker_idx < checker_end && val[checker.checker_idx] !in [`,`, `:`, `}`, `]`] { - // get trash characters after the value - if val[checker.checker_idx] !in whitespace_chars { - checker.checker_error('invalid value. Unexpected character after ${value_kind} end')! - } else { - // whitespace - } - checker.checker_idx++ - } -} - -// get_value_kind returns the kind of a JSON value. -fn get_value_kind(value u8) ValueKind { - if value == u8(`"`) { - return .string_ - } else if value == u8(`t`) || value == u8(`f`) { - return .boolean - } else if value == u8(`{`) { - return .object - } else if value == u8(`[`) { - return .array - } else if (value >= u8(48) && value <= u8(57)) || value == u8(`-`) { - return .number - } else if value == u8(`n`) { - return .null - } - return .unknown -} - // decode decodes a JSON string into a specified type. @[manualfree] pub fn decode[T](val string) !T { @@ -717,7 +294,7 @@ pub fn decode[T](val string) !T { json: val } - decoder.check_json_format(val)! + decoder.check_json_format()! mut result := T{} decoder.current_node = decoder.values_info.head @@ -728,6 +305,10 @@ pub fn decode[T](val string) !T { return result } +fn get_dynamic_from_element[T](t T) []T { + return []T{} +} + // decode_value decodes a value from the JSON nodes. @[manualfree] fn (mut decoder Decoder) decode_value[T](mut val T) ! { @@ -862,16 +443,29 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { } $else $if T.unaliased_typ is $map { decoder.decode_map(mut val)! return - } $else $if T.unaliased_typ is $array { - unsafe { - val.len = 0 - } + } $else $if T.unaliased_typ is $array_dynamic { + val.clear() decoder.decode_array(mut val)! // return to avoid the next increment of the current node // this is because the current node is already incremented in the decode_array function // remove this line will cause the current node to be incremented twice // and bug recursive array decoding like `[][]int{}` return + } $else $if T.unaliased_typ is $array_fixed { + mut dynamic_val := get_dynamic_from_element(val[0]) + + // avoid copying by pointing dynamic_val to val + unsafe { + dynamic_val.len = 0 + dynamic_val.cap = val.len // ensures data wont reallocate + dynamic_val.data = &val + } + decoder.decode_array(mut dynamic_val)! + + if dynamic_val.len != val.len { + decoder.decode_error('Fixed size array expected ${val.len} elements but got ${dynamic_val.len} elements')! + } + return } $else $if T.unaliased_typ is $struct { struct_info := decoder.current_node.value @@ -927,8 +521,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { current_field_info = struct_fields_info.head - mut field_used := false - // field loop for { if current_field_info == unsafe { nil } { @@ -939,7 +531,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { if current_field_info.value.is_skip { if current_field_info.value.is_required == false { - field_used = true current_field_info = current_field_info.next continue } @@ -948,13 +539,11 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { if current_field_info.value.is_omitempty { match decoder.current_node.next.value.value_kind { .null { - field_used = true current_field_info = current_field_info.next continue } .string_ { if decoder.current_node.next.value.length == 2 { - field_used = true current_field_info = current_field_info.next continue } @@ -962,7 +551,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { .number { if decoder.json[decoder.current_node.next.value.position] == `0` { if decoder.current_node.next.value.length == 1 { - field_used = true current_field_info = current_field_info.next continue } else if decoder.current_node.next.value.length == 3 { @@ -971,7 +559,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { decoder.current_node.next.value.position, float_zero_in_string.str, float_zero_in_string.len) == 0 } { - field_used = true current_field_info = current_field_info.next continue } @@ -988,8 +575,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { vmemcmp(decoder.json.str + key_info.position + 1, current_field_info.value.json_name_ptr, current_field_info.value.json_name_len) == 0 } { - field_used = true - $for field in T.fields { if field.name.len == current_field_info.value.field_name_len { if unsafe { @@ -1076,12 +661,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { } current_field_info = current_field_info.next } - - if !field_used - && decoder.json[key_info.position + 1..key_info.position + key_info.length - 1] != '_type' { - decoder.decode_error('unknown field `${decoder.json[key_info.position.. - key_info.position + key_info.length]}`')! - } } // check if all required fields are present @@ -1125,6 +704,12 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { value_info := decoder.current_node.value if value_info.value_kind == .number { + unsafe { decoder.decode_number(&val)! } + } else if value_info.value_kind == .string_ { + // recheck if string contains number + decoder.checker_idx = value_info.position + 1 + decoder.check_number()! + unsafe { decoder.decode_number(&val)! } } else { return decoder.decode_error('Expected number, but got ${value_info.value_kind}') @@ -1277,7 +862,14 @@ fn get_number_digits[T](num T) int { // use pointer instead of mut so enum cast works @[unsafe] fn (mut decoder Decoder) decode_number[T](val &T) ! { - number_info := decoder.current_node.value + mut number_info := decoder.current_node.value + + if decoder.json[number_info.position] == `"` { // fake number + number_info = ValueInfo{ + position: number_info.position + 1 + length: number_info.length - 2 + } + } $if T.unaliased_typ is $float { *val = T(strconv.atof_quick(decoder.json[number_info.position..number_info.position + diff --git a/vlib/x/json2/decoder2/decode_sumtype.v b/vlib/x/json2/decoder2/decode_sumtype.v index 6f008eb691..a4a4733d92 100644 --- a/vlib/x/json2/decoder2/decode_sumtype.v +++ b/vlib/x/json2/decoder2/decode_sumtype.v @@ -92,7 +92,6 @@ fn (mut decoder Decoder) check_element_type_valid[T](element T, current_node &No return decoder.check_struct_type_valid(element, current_node) } } - else {} } return false @@ -304,7 +303,6 @@ fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info Val } } } - else {} } if failed_struct { diff --git a/vlib/x/json2/decoder2/decode_test.v b/vlib/x/json2/decoder2/decode_test.v deleted file mode 100644 index fed0050bce..0000000000 --- a/vlib/x/json2/decoder2/decode_test.v +++ /dev/null @@ -1,230 +0,0 @@ -module decoder2 - -fn test_check_if_json_match() { - // /* Test wrong string values */ - mut has_error := false - - decode[string]('{"key": "value"}') or { - if err is JsonDecodeError { - assert err.line == 1 - assert err.character == 1 - assert err.message == 'Data: Expected string, but got object' - } - has_error = true - } - assert has_error, 'Expected error' - has_error = false - - decode[map[string]string]('"value"') or { - if err is JsonDecodeError { - assert err.line == 1 - assert err.character == 1 - assert err.message == 'Data: Expected object, but got string_' - } - has_error = true - } - assert has_error, 'Expected error' - has_error = false - - decode[[]int]('{"key": "value"}') or { - if err is JsonDecodeError { - assert err.line == 1 - assert err.character == 1 - assert err.message == 'Data: Expected array, but got object' - } - has_error = true - } - assert has_error, 'Expected error' - has_error = false - - decode[string]('[1, 2, 3]') or { - if err is JsonDecodeError { - assert err.line == 1 - assert err.character == 1 - assert err.message == 'Data: Expected string, but got array' - } - has_error = true - } - assert has_error, 'Expected error' - has_error = false - - decode[int]('{"key": "value"}') or { - if err is JsonDecodeError { - assert err.line == 1 - assert err.character == 1 - assert err.message == 'Data: Expected number, but got object' - } - has_error = true - } - assert has_error, 'Expected error' - has_error = false - - decode[bool]('{"key": "value"}') or { - if err is JsonDecodeError { - assert err.line == 1 - assert err.character == 1 - assert err.message == 'Data: Expected boolean, but got object' - } - has_error = true - } - assert has_error, 'Expected error' - has_error = false - - // /* Right string values */ - decode[string]('"value"') or { assert false } - - decode[map[string]string]('{"key": "value"}') or { assert false } - - decode[[]int]('[1, 2, 3]') or { assert false } - - decode[string]('"string"') or { assert false } - - decode[int]('123') or { assert false } - - decode[bool]('true') or { assert false } - - decode[bool]('false') or { assert false } - - // TODO: test null -} - -fn test_check_json_format() { - // primitives - for variable in ['""', '"string"', '123', '0', 'true'] { - mut checker := Decoder{ - checker_idx: 0 - json: variable - } - - checker.check_json_format(variable) or { assert false, err.str() } - assert checker.checker_idx == checker.json.len, 'Expected to reach the end of the json string ${checker.json}' - } - - // simple objects - for variable in ['{}', '{"key": null}', '{"key": "value"}', '{"key": 123}', '{"key": true}'] { - mut checker := Decoder{ - checker_idx: 0 - json: variable - } - - checker.check_json_format(variable) or { assert false, err.str() } - assert checker.checker_idx == checker.json.len, 'Expected to reach the end of the json string ${checker.json}' - } - - // Nested objects - for variable in ['{"key": {"key": 123}}'] { - mut checker := Decoder{ - checker_idx: 0 - json: variable - } - - checker.check_json_format(variable) or { assert false, err.str() } - assert checker.checker_idx == checker.json.len, 'Expected to reach the end of the json string ${checker.json}' - } - - // simple arrays - for variable in ['[]', '[1, 2, 3]', '["a", "b", "c"]', '[true, false]'] { - mut checker := Decoder{ - checker_idx: 0 - json: variable - } - - checker.check_json_format(variable) or { assert false, err.str() } - assert checker.checker_idx == checker.json.len, 'Expected to reach the end of the json string ${checker.json}' - } - - // Nested arrays - for variable in ['[[1, 2, 3], [4, 5, 6]]'] { - mut checker := Decoder{ - checker_idx: 0 - json: variable - } - - checker.check_json_format(variable) or { assert false, err.str() } - // assert checker.checker_idx == checker.json.len - 1, 'Expected to reach the end of the json string ${checker.json}' - } - - // Wrong jsons - - json_and_error_message := [ - { - 'json': ']' - 'error': 'Syntax: unknown value kind' - }, - { - 'json': '}' - 'error': 'Syntax: unknown value kind' - }, - { - 'json': 'truely' - 'error': 'Syntax: invalid value. Unexpected character after boolean end' - }, - { - 'json': '0[1]' - 'error': 'Syntax: invalid value. Unexpected character after number end' - }, - { - 'json': '[1, 2, g3]' - 'error': 'Syntax: unknown value kind' - }, - { - 'json': '[1, 2,, 3]' - 'error': 'Syntax: unknown value kind' - }, - { - 'json': '{"key": 123' - 'error': 'Syntax: EOF error: braces are not closed' - }, - { - 'json': '{"key": 123,' - 'error': 'Syntax: EOF error: Expecting object key after `,`' - }, - { - 'json': '{"key": 123, "key2": 456,}' - 'error': 'Syntax: Expecting object key after `,`' - }, - { - 'json': '[[1, 2, 3], [4, 5, 6],]' - 'error': 'Syntax: Cannot use `,`, before `]`' - }, - ] - - for json_and_error in json_and_error_message { - mut has_error := false - mut checker := Decoder{ - checker_idx: 0 - json: json_and_error['json'] - } - - checker.check_json_format(json_and_error['json']) or { - if err is JsonDecodeError { - assert err.message == json_and_error['error'] - } - has_error = true - } - assert has_error, 'Expected error ${json_and_error['error']}' - } -} - -fn test_get_value_kind() { - struct Object_ { - byte_ u8 - value_kind ValueKind - } - - array_ := [ - Object_{`"`, .string_}, - Object_{`t`, .boolean}, - Object_{`f`, .boolean}, - Object_{`{`, .object}, - Object_{`[`, .array}, - Object_{`0`, .number}, - Object_{`-`, .number}, - Object_{`n`, .null}, - Object_{`x`, .unknown}, - ] - - for value in array_ { - assert get_value_kind(value.byte_) == value.value_kind - } -} diff --git a/vlib/x/json2/decoder2/tests/checker_test.v b/vlib/x/json2/decoder2/tests/checker_test.v new file mode 100644 index 0000000000..1ede6e7b3b --- /dev/null +++ b/vlib/x/json2/decoder2/tests/checker_test.v @@ -0,0 +1,145 @@ +import x.json2.decoder2 as json +import x.json2 + +fn test_check_if_json_match() { + // /* Test wrong string values */ + mut has_error := false + + json.decode[string]('{"key": "value"}') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected string, but got object' + } + has_error = true + } + assert has_error, 'Expected error' + has_error = false + + json.decode[map[string]string]('"value"') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected object, but got string_' + } + has_error = true + } + assert has_error, 'Expected error' + has_error = false + + json.decode[[]int]('{"key": "value"}') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected array, but got object' + } + has_error = true + } + assert has_error, 'Expected error' + has_error = false + + json.decode[string]('[1, 2, 3]') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected string, but got array' + } + has_error = true + } + assert has_error, 'Expected error' + has_error = false + + json.decode[int]('{"key": "value"}') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected number, but got object' + } + has_error = true + } + assert has_error, 'Expected error' + has_error = false + + json.decode[bool]('{"key": "value"}') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected boolean, but got object' + } + has_error = true + } + assert has_error, 'Expected error' + has_error = false + + // /* Right string values */ + json.decode[string]('"value"') or { assert false } + + json.decode[map[string]string]('{"key": "value"}') or { assert false } + + json.decode[[]int]('[1, 2, 3]') or { assert false } + + json.decode[string]('"string"') or { assert false } + + json.decode[int]('123') or { assert false } + + json.decode[bool]('true') or { assert false } + + json.decode[bool]('false') or { assert false } +} + +fn test_check_json_format() { + json_and_error_message := [ + { + 'json': ']' + 'error': 'Syntax: unknown value kind' + }, + { + 'json': '}' + 'error': 'Syntax: unknown value kind' + }, + { + 'json': 'truely' + 'error': 'Syntax: invalid value. Unexpected character after boolean end' + }, + { + 'json': '0[1]' + 'error': 'Syntax: invalid value. Unexpected character after number end' + }, + { + 'json': '[1, 2, g3]' + 'error': 'Syntax: unknown value kind' + }, + { + 'json': '[1, 2,, 3]' + 'error': 'Syntax: unknown value kind' + }, + { + 'json': '{"key": 123' + 'error': 'Syntax: EOF error: braces are not closed' + }, + { + 'json': '{"key": 123,' + 'error': 'Syntax: EOF error: Expecting object key after `,`' + }, + { + 'json': '{"key": 123, "key2": 456,}' + 'error': 'Syntax: Expecting object key after `,`' + }, + { + 'json': '[[1, 2, 3], [4, 5, 6],]' + 'error': 'Syntax: Cannot use `,`, before `]`' + }, + ] + + for json_and_error in json_and_error_message { + mut has_error := false + + json.decode[json2.Any](json_and_error['json']) or { + if err is json.JsonDecodeError { + assert err.message == json_and_error['error'] + } + has_error = true + } + assert has_error, 'Expected error ${json_and_error['error']}' + } +} diff --git a/vlib/x/json2/decoder2/tests/decode_budget_number_test.v b/vlib/x/json2/decoder2/tests/decode_budget_number_test.v new file mode 100644 index 0000000000..dc43f5af22 --- /dev/null +++ b/vlib/x/json2/decoder2/tests/decode_budget_number_test.v @@ -0,0 +1,23 @@ +import x.json2.decoder2 as json + +fn test_budget_number() { + assert json.decode[int]('"0"')! == 0 + assert json.decode[int]('"100"')! == 100 + assert json.decode[f64]('"-23.6e1"')! == -236.0 + + assert json.decode[[]int]('["100", 99, "98", 97]')! == [100, 99, 98, 97] +} + +fn test_budget_number_malformed() { + json.decode[int]('"+100"') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 2 + assert err.message == 'Syntax: expected digit got +' + } + + return + } + + assert false +} diff --git a/vlib/x/json2/decoder2/tests/decode_fixed_array_test.v b/vlib/x/json2/decoder2/tests/decode_fixed_array_test.v new file mode 100644 index 0000000000..f9caba545f --- /dev/null +++ b/vlib/x/json2/decoder2/tests/decode_fixed_array_test.v @@ -0,0 +1,35 @@ +import x.json2.decoder2 as json + +fn test_fixed_array() { + mut expected := [3]int{} + expected[0] = 1 + expected[1] = 2 + expected[2] = 3 + assert json.decode[[3]int]('[1, 2, 3]')! == expected +} + +fn test_fixed_array_to_few() { + json.decode[[4]int]('[1, 2, 3]') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 8 + assert err.message == 'Data: Fixed size array expected 4 elements but got 3 elements' + } + + return + } + assert false +} + +fn test_fixed_array_to_many() { + json.decode[[2]int]('[1, 2, 3]') or { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 8 + assert err.message == 'Data: Fixed size array expected 2 elements but got 3 elements' + } + + return + } + assert false +} diff --git a/vlib/x/json2/types.v b/vlib/x/json2/types.v index 19f52d053f..4d54755629 100644 --- a/vlib/x/json2/types.v +++ b/vlib/x/json2/types.v @@ -23,11 +23,6 @@ pub type Any = []Any | u8 | Null -// Decodable is an interface, that allows custom implementations for decoding structs from JSON encoded values. -pub interface Decodable { - from_json(f Any) -} - // Encodable is an interface, that allows custom implementations for encoding structs to their string based JSON representations. pub interface Encodable { json_str() string @@ -45,21 +40,25 @@ pub const null = Null{} pub fn (mut n Null) from_json_null() {} // ValueKind enumerates the kinds of possible values of the Any sumtype. -pub enum ValueKind { +enum ValueKind { unknown array object string_ number + boolean + null } // str returns the string representation of the specific ValueKind. -pub fn (k ValueKind) str() string { +fn (k ValueKind) str() string { return match k { .unknown { 'unknown' } .array { 'array' } .object { 'object' } .string_ { 'string' } .number { 'number' } + .boolean { 'boolean' } + .null { 'null' } } }