From cb28144eeab8cae35f5b69bc4ef69ef1a499a788 Mon Sep 17 00:00:00 2001 From: Hitalo Souza <63821277+enghitalo@users.noreply.github.com> Date: Mon, 1 Jan 2024 07:56:19 -0400 Subject: [PATCH] json2: strict module (#17927) --- vlib/x/json2/strict/strict.v | 185 ++++++++++++++++++++++++ vlib/x/json2/strict/strict_test.v | 48 ++++++ vlib/x/json2/strict/strict_test_todo.vv | 29 ++++ 3 files changed, 262 insertions(+) create mode 100644 vlib/x/json2/strict/strict.v create mode 100644 vlib/x/json2/strict/strict_test.v create mode 100644 vlib/x/json2/strict/strict_test_todo.vv diff --git a/vlib/x/json2/strict/strict.v b/vlib/x/json2/strict/strict.v new file mode 100644 index 0000000000..0c373287d5 --- /dev/null +++ b/vlib/x/json2/strict/strict.v @@ -0,0 +1,185 @@ +module strict + +import arrays + +pub struct KeyStruct { +pub: + key string + value_type KeyType + token_pos int // the position of the token +} + +pub enum KeyType { + literal + map + array +} + +pub struct StructCheckResult { + duplicates []string + superfluous []string +} + +// strict_check +pub fn strict_check[T](json_data string) StructCheckResult { + // REVIEW how performatic is it? + $if T is $struct { + tokens := tokenize(json_data) + + key_struct := get_keys_from_json(tokens) + + mut duplicates := get_duplicates_keys(key_struct) + mut superfluous := get_superfluous_keys[T](key_struct) + + mut val := T{} + + $for field in T.fields { + $if field.typ is $struct { + field_name := field.name + last_key := arrays.find_last(key_struct, fn [field_name] (k KeyStruct) bool { + return k.key == field_name + }) or { panic('${field.name} not found') } + + // TODO get path here from `last_key.key` + if last_key.value_type == .map { + check(val.$(field.name), tokens[last_key.token_pos + 2..], mut duplicates, mut + superfluous) + } + } + } + return StructCheckResult{ + duplicates: duplicates + superfluous: superfluous + } + } $else { + return StructCheckResult{} + } +} + +fn check[T](val T, tokens []string, mut duplicates []string, mut superfluous []string) { + $if T is $struct { + key_struct := get_keys_from_json(tokens) + + for duplicate in get_duplicates_keys(key_struct) { + duplicates << duplicate + } + + for unnecessary in get_superfluous_keys[T](key_struct) { + superfluous << unnecessary + } + + $for field in T.fields { + $if field.typ is $struct { + if last_key.value_type == .map { + check(val.$(field.name), tokens[last_key.token_pos + 2..], mut duplicates, mut + superfluous) + } + } + } + } +} + +fn get_superfluous_keys[T](key_struct []KeyStruct) []string { + mut superfluous := []string{} + + struct_keys := get_keys_from_[T]() + + json_keys := key_struct.map(fn (json_key KeyStruct) string { + return json_key.key + }) + + for json_key in json_keys { + if !struct_keys.contains(json_key) { + superfluous << json_key + } + } + return superfluous +} + +fn get_duplicates_keys(key_struct []KeyStruct) []string { + json_keys := key_struct.map(it.key).sorted() + return arrays.uniq_only_repeated(json_keys) +} + +fn get_keys_from_[T]() []string { + mut struct_keys := []string{} + $if T is $struct { + $for field in T.fields { + struct_keys << field.name + } + } + return struct_keys +} + +// get_keys_from_json +pub fn get_keys_from_json(tokens []string) []KeyStruct { + mut key_structs := []KeyStruct{} + + mut nested_map_count := 0 + + for i, token in tokens { + if token == ':' { + mut current_key := tokens[i - 1].replace('"', '') + if tokens[i + 1] == '{' { + if nested_map_count == 0 { + key_type := KeyType.map + key_structs << KeyStruct{ + key: current_key + value_type: key_type + token_pos: i - 1 + } + } + nested_map_count++ + } else if tokens[i + 1] == '[' { + continue + } else if nested_map_count > 0 { + if tokens[i + 1] == '}' { + nested_map_count-- + } else { + // REVIEW Não sei + } + } else { + key_type := KeyType.literal + key_structs << KeyStruct{ + key: current_key + value_type: key_type + token_pos: i - 1 + } + } + } + } + + return key_structs +} + +fn tokenize(json_data string) []string { + mut tokens := []string{} + mut current_token := '' + mut inside_string := false + + for letter in json_data.replace('\n', ' ').replace('\t', ' ') { + if letter == ` ` && !inside_string { + if current_token != '' { + tokens << current_token + current_token = '' + } + } else if letter == `\"` { + inside_string = !inside_string + current_token += '"' + } else if letter == `,` || letter == `:` || letter == `{` || letter == `}` || letter == `[` + || letter == `]` { + if current_token != '' { + tokens << current_token + current_token = '' + } + tokens << [letter].bytestr() + } else { + current_token += [letter].bytestr() + } + } + + if current_token != '' { + tokens << current_token + } + return tokens +} diff --git a/vlib/x/json2/strict/strict_test.v b/vlib/x/json2/strict/strict_test.v new file mode 100644 index 0000000000..02716cc342 --- /dev/null +++ b/vlib/x/json2/strict/strict_test.v @@ -0,0 +1,48 @@ +import x.json2.strict + +struct StructType[T] { +mut: + val T +} + +struct StructTypeAndOptionType[T] { +mut: + val T + option_val ?T +} + +fn test_get_keys_from_json() { + json_data := r' + { + "val": 0, + "val1": {"val": 63} + } + ' + + key_structs := strict.get_keys_from_json(strict.tokenize(json_data)) + + assert key_structs == [ + strict.KeyStruct{ + key: 'val' + value_type: .literal + token_pos: 1 + }, + strict.KeyStruct{ + key: 'val1' + value_type: .map + token_pos: 5 + }, + ] +} + +fn test_strict_check() { + assert strict.strict_check[StructTypeAndOptionType[string]]('{"val": "","val": ""}') == strict.StructCheckResult{ + duplicates: ['val'] + superfluous: [] + } + + assert strict.strict_check[StructTypeAndOptionType[string]]('{"val": "","val2": ""}') == strict.StructCheckResult{ + duplicates: [] + superfluous: ['val2'] + } +} diff --git a/vlib/x/json2/strict/strict_test_todo.vv b/vlib/x/json2/strict/strict_test_todo.vv new file mode 100644 index 0000000000..232f180a44 --- /dev/null +++ b/vlib/x/json2/strict/strict_test_todo.vv @@ -0,0 +1,29 @@ +import x.json2.strict + +struct StructType[T] { +mut: + val T +} + +struct StructTypeAndOptionType[T] { +mut: + val T + option_val ?T +} + +fn test_strict_check() { + assert strict.strict_check[StructTypeAndOptionType[string]]('{"val": "","val2": "","val3": "","val3": ""}') == strict.StructCheckResult{ + duplicates: ['val3'] + superfluous: ['val2', 'val3'] + } + + assert strict.strict_check[StructType[StructTypeAndOptionType[string]]]('{"val": {"val": "","val2": ""}}') == strict.StructCheckResult{ + duplicates: [] + superfluous: ['val.val2'] + } + + assert strict.strict_check[StructType[[]StructTypeAndOptionType[string]]]('{"val": [{"val": "","val2": ""}],[{"val": "","gdgd": "sss"}]}') == strict.StructCheckResult{ + duplicates: [] + superfluous: ['val[0].val2', 'val[1].gdgd'] + } +}