json: use @[required] to disallow parsing nulls (#23218)

This commit is contained in:
Carlos Esquerdo Bernat 2024-12-22 09:12:58 +01:00 committed by GitHub
parent 1ca902fcc4
commit 63fff1dcd4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 11 deletions

View file

@ -4692,7 +4692,7 @@ struct User {
last_name string @[json: lastName]
}
data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25 }'
data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25, "nullable": null }'
user := json.decode(User, data) or {
eprintln('Failed to decode json, error: ${err}')
return

View file

@ -516,12 +516,9 @@ fn test_encode_alias_field() {
a: 1
}
})
println(s)
assert s == '{"sub":{"a":1}}'
}
//
struct APrice {}
struct Association {
@ -538,3 +535,55 @@ fn test_encoding_struct_with_pointers() {
}
assert json.encode(value) == '{"association":{"price":{}},"price":{}}'
}
struct ChildNullish {
name string
}
struct NullishStruct {
name string
lastname string
age int
salary f32
child ChildNullish
}
struct RequiredStruct {
name string @[required]
lastname string
}
fn test_required() {
nullish_one := json.decode(NullishStruct, '{"name":"Peter", "lastname":null, "age":28,"salary":95000.5,"title":"worker"}')!
assert nullish_one.name == 'Peter'
assert nullish_one.lastname == ''
assert nullish_one.age == 28
assert nullish_one.salary == 95000.5
assert nullish_one.child.name == ''
nullish_two := json.decode(NullishStruct, '{"name":"Peter", "lastname": "Parker", "age":28,"salary":95000.5,"title":"worker"}')!
assert nullish_two.name == 'Peter'
assert nullish_two.lastname == 'Parker'
assert nullish_two.age == 28
assert nullish_two.salary == 95000.5
assert nullish_two.child.name == ''
nullish_third := json.decode(NullishStruct, '{"name":"Peter", "age":28,"salary":95000.5,"title":"worker", "child": {"name":"Nullish"}}')!
assert nullish_third.name == 'Peter'
assert nullish_third.lastname == ''
assert nullish_third.age == 28
assert nullish_third.salary == 95000.5
assert nullish_third.child.name == 'Nullish'
required_struct := json.decode(RequiredStruct, '{"name":"Peter", "lastname": "Parker"}')!
assert required_struct.name == 'Peter'
assert required_struct.lastname == 'Parker'
required_struct_err := json.decode(RequiredStruct, '{"name": null, "lastname": "Parker"}') or {
assert err.msg() == "type mismatch for field 'name', expecting `string` type, got: null"
RequiredStruct{
name: 'Peter'
lastname: 'Parker'
}
}
}

View file

@ -627,8 +627,12 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
}
}
fn (mut g Gen) gen_prim_type_validation(name string, typ ast.Type, tmp string, ret_styp string, mut dec strings.Builder) {
none_check := if typ.has_flag(.option) { 'cJSON_IsNull(jsonroot_${tmp}) || ' } else { '' }
fn (mut g Gen) gen_prim_type_validation(name string, typ ast.Type, tmp string, is_required bool, ret_styp string, mut dec strings.Builder) {
none_check := if !is_required {
'cJSON_IsNull(jsonroot_${tmp}) || '
} else {
''
}
type_check := if typ.is_int() || typ.is_float() {
'${none_check}cJSON_IsNumber(jsonroot_${tmp}) || (cJSON_IsString(jsonroot_${tmp}) && strlen(jsonroot_${tmp}->valuestring))'
} else if typ.is_string() {
@ -724,7 +728,7 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
if utyp.has_flag(.option) {
dec.writeln('\t\tres.state = 0;')
}
g.gen_prim_type_validation(field.name, field.typ, tmp, '${result_name}_${styp}', mut
g.gen_prim_type_validation(field.name, field.typ, tmp, is_required, '${result_name}_${styp}', mut
dec)
dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${dec_name}(jsonroot_${tmp});')
if field.has_default_expr {
@ -791,8 +795,8 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
tmp := g.new_tmp_var()
gen_js_get(styp, tmp, name, mut dec, is_required)
dec.writeln('\tif (jsonroot_${tmp}) {')
g.gen_prim_type_validation(field.name, parent_type, tmp, '${result_name}_${styp}', mut
dec)
g.gen_prim_type_validation(field.name, parent_type, tmp, is_required,
'${result_name}_${styp}', mut dec)
dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${parent_dec_name} (jsonroot_${tmp});')
if field.has_default_expr {
dec.writeln('\t} else {')
@ -832,8 +836,8 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st
gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required)
dec.writeln('\tif (jsonroot_${tmp}) {')
if is_js_prim(g.styp(field.typ.clear_option_and_result())) {
g.gen_prim_type_validation(field.name, field.typ, tmp, '${result_name}_${styp}', mut
dec)
g.gen_prim_type_validation(field.name, field.typ, tmp, is_required,
'${result_name}_${styp}', mut dec)
}
if field.typ.has_flag(.option) {
dec.writeln('\t\tvmemcpy(&${prefix}${op}${c_name(field.name)}, (${field_type}*)${tmp}.data, sizeof(${field_type}));')