mirror of
https://github.com/vlang/v.git
synced 2025-09-13 17:36:52 -04:00
json2: strict module (#17927)
This commit is contained in:
parent
41d38d735a
commit
cb28144eea
185
vlib/x/json2/strict/strict.v
Normal file
185
vlib/x/json2/strict/strict.v
Normal file
@ -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
|
||||
}
|
48
vlib/x/json2/strict/strict_test.v
Normal file
48
vlib/x/json2/strict/strict_test.v
Normal file
@ -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']
|
||||
}
|
||||
}
|
29
vlib/x/json2/strict/strict_test_todo.vv
Normal file
29
vlib/x/json2/strict/strict_test_todo.vv
Normal file
@ -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']
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user