Skip to content

Commit 63fff1d

Browse files
authored
json: use @[required] to disallow parsing nulls (#23218)
1 parent 1ca902f commit 63fff1d

File tree

3 files changed

+64
-11
lines changed

3 files changed

+64
-11
lines changed

doc/docs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4692,7 +4692,7 @@ struct User {
46924692
last_name string @[json: lastName]
46934693
}
46944694
4695-
data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25 }'
4695+
data := '{ "name": "Frodo", "lastName": "Baggins", "age": 25, "nullable": null }'
46964696
user := json.decode(User, data) or {
46974697
eprintln('Failed to decode json, error: ${err}')
46984698
return

vlib/json/tests/json_test.v

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -516,12 +516,9 @@ fn test_encode_alias_field() {
516516
a: 1
517517
}
518518
})
519-
println(s)
520519
assert s == '{"sub":{"a":1}}'
521520
}
522521

523-
//
524-
525522
struct APrice {}
526523

527524
struct Association {
@@ -538,3 +535,55 @@ fn test_encoding_struct_with_pointers() {
538535
}
539536
assert json.encode(value) == '{"association":{"price":{}},"price":{}}'
540537
}
538+
539+
struct ChildNullish {
540+
name string
541+
}
542+
543+
struct NullishStruct {
544+
name string
545+
lastname string
546+
age int
547+
salary f32
548+
child ChildNullish
549+
}
550+
551+
struct RequiredStruct {
552+
name string @[required]
553+
lastname string
554+
}
555+
556+
fn test_required() {
557+
nullish_one := json.decode(NullishStruct, '{"name":"Peter", "lastname":null, "age":28,"salary":95000.5,"title":"worker"}')!
558+
assert nullish_one.name == 'Peter'
559+
assert nullish_one.lastname == ''
560+
assert nullish_one.age == 28
561+
assert nullish_one.salary == 95000.5
562+
assert nullish_one.child.name == ''
563+
564+
nullish_two := json.decode(NullishStruct, '{"name":"Peter", "lastname": "Parker", "age":28,"salary":95000.5,"title":"worker"}')!
565+
assert nullish_two.name == 'Peter'
566+
assert nullish_two.lastname == 'Parker'
567+
assert nullish_two.age == 28
568+
assert nullish_two.salary == 95000.5
569+
assert nullish_two.child.name == ''
570+
571+
nullish_third := json.decode(NullishStruct, '{"name":"Peter", "age":28,"salary":95000.5,"title":"worker", "child": {"name":"Nullish"}}')!
572+
assert nullish_third.name == 'Peter'
573+
assert nullish_third.lastname == ''
574+
assert nullish_third.age == 28
575+
assert nullish_third.salary == 95000.5
576+
assert nullish_third.child.name == 'Nullish'
577+
578+
required_struct := json.decode(RequiredStruct, '{"name":"Peter", "lastname": "Parker"}')!
579+
assert required_struct.name == 'Peter'
580+
assert required_struct.lastname == 'Parker'
581+
582+
required_struct_err := json.decode(RequiredStruct, '{"name": null, "lastname": "Parker"}') or {
583+
assert err.msg() == "type mismatch for field 'name', expecting `string` type, got: null"
584+
RequiredStruct{
585+
name: 'Peter'
586+
lastname: 'Parker'
587+
}
588+
}
589+
}

vlib/v/gen/c/json.v

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -627,8 +627,12 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st
627627
}
628628
}
629629

630-
fn (mut g Gen) gen_prim_type_validation(name string, typ ast.Type, tmp string, ret_styp string, mut dec strings.Builder) {
631-
none_check := if typ.has_flag(.option) { 'cJSON_IsNull(jsonroot_${tmp}) || ' } else { '' }
630+
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) {
631+
none_check := if !is_required {
632+
'cJSON_IsNull(jsonroot_${tmp}) || '
633+
} else {
634+
''
635+
}
632636
type_check := if typ.is_int() || typ.is_float() {
633637
'${none_check}cJSON_IsNumber(jsonroot_${tmp}) || (cJSON_IsString(jsonroot_${tmp}) && strlen(jsonroot_${tmp}->valuestring))'
634638
} 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
724728
if utyp.has_flag(.option) {
725729
dec.writeln('\t\tres.state = 0;')
726730
}
727-
g.gen_prim_type_validation(field.name, field.typ, tmp, '${result_name}_${styp}', mut
731+
g.gen_prim_type_validation(field.name, field.typ, tmp, is_required, '${result_name}_${styp}', mut
728732
dec)
729733
dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${dec_name}(jsonroot_${tmp});')
730734
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
791795
tmp := g.new_tmp_var()
792796
gen_js_get(styp, tmp, name, mut dec, is_required)
793797
dec.writeln('\tif (jsonroot_${tmp}) {')
794-
g.gen_prim_type_validation(field.name, parent_type, tmp, '${result_name}_${styp}', mut
795-
dec)
798+
g.gen_prim_type_validation(field.name, parent_type, tmp, is_required,
799+
'${result_name}_${styp}', mut dec)
796800
dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${parent_dec_name} (jsonroot_${tmp});')
797801
if field.has_default_expr {
798802
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
832836
gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required)
833837
dec.writeln('\tif (jsonroot_${tmp}) {')
834838
if is_js_prim(g.styp(field.typ.clear_option_and_result())) {
835-
g.gen_prim_type_validation(field.name, field.typ, tmp, '${result_name}_${styp}', mut
836-
dec)
839+
g.gen_prim_type_validation(field.name, field.typ, tmp, is_required,
840+
'${result_name}_${styp}', mut dec)
837841
}
838842
if field.typ.has_flag(.option) {
839843
dec.writeln('\t\tvmemcpy(&${prefix}${op}${c_name(field.name)}, (${field_type}*)${tmp}.data, sizeof(${field_type}));')

0 commit comments

Comments
 (0)