From 11bdb04d0cd6f6fea789bbdd30db1a404ad390b8 Mon Sep 17 00:00:00 2001 From: StunxFS <56417208+StunxFS@users.noreply.github.com> Date: Fri, 20 May 2022 04:22:17 -0400 Subject: [PATCH] json: fix struct field default value support (#14304) --- vlib/json/json_primitives.v | 13 ++++++- vlib/json/json_test.v | 27 +++++++++++++ vlib/v/ast/types.v | 1 - vlib/v/gen/c/json.v | 78 ++++++++++++++++++++++++++++++------- vlib/v/parser/struct.v | 9 +---- 5 files changed, 103 insertions(+), 25 deletions(-) diff --git a/vlib/json/json_primitives.v b/vlib/json/json_primitives.v index f8868b67e1..68c1f9b8f0 100644 --- a/vlib/json/json_primitives.v +++ b/vlib/json/json_primitives.v @@ -71,6 +71,11 @@ fn decode_i64(root &C.cJSON) i64 { return i64(root.valuedouble) // i64 is double in C } +// TODO: remove when `byte` is removed +fn decode_byte(root &C.cJSON) byte { + return byte(decode_u8(root)) +} + fn decode_u8(root &C.cJSON) u8 { if isnil(root) { return u8(0) @@ -132,8 +137,6 @@ fn decode_string(root &C.cJSON) string { if isnil(root.valuestring) { return '' } - // println('decode string valuestring="$root.valuestring"') - // return tos(root.valuestring, _strlen(root.valuestring)) return unsafe { tos_clone(&u8(root.valuestring)) } // , _strlen(root.valuestring)) } @@ -145,6 +148,7 @@ fn decode_bool(root &C.cJSON) bool { } // /////////////////// + fn encode_int(val int) &C.cJSON { return C.cJSON_CreateNumber(val) } @@ -161,6 +165,11 @@ fn encode_i64(val i64) &C.cJSON { return C.cJSON_CreateNumber(val) } +// TODO: remove when `byte` is removed +fn encode_byte(root byte) &C.cJSON { + return encode_u8(u8(root)) +} + fn encode_u8(val u8) &C.cJSON { return C.cJSON_CreateNumber(val) } diff --git a/vlib/json/json_test.v b/vlib/json/json_test.v index 2cb15870b1..dd72c0c469 100644 --- a/vlib/json/json_test.v +++ b/vlib/json/json_test.v @@ -27,6 +27,25 @@ fn test_simple() ? { assert y.title == .worker } +const currency_id = 'cconst' + +struct Price { + net f64 + currency_id string [json: currencyId] = currency_id +} + +fn test_field_with_default_expr() ? { + data := '[{"net":1},{"net":2,"currencyId":"cjson"}]' + prices := json.decode([]Price, data)? + assert prices == [Price{ + net: 1 + currency_id: 'cconst' + }, Price{ + net: 2 + currency_id: 'cjson' + }] +} + fn test_decode_top_level_array() { s := '[{"name":"Peter", "age": 29}, {"name":"Bob", "age":31}]' x := json.decode([]Employee, s) or { panic(err) } @@ -454,3 +473,11 @@ fn test_encode_sumtype_defined_ahead() { println(ret) assert ret == '{"value":0,"_type":"GPScale"}' } + +struct StByteArray { + ba []byte +} + +fn test_byte_array() { + assert json.encode(StByteArray{ ba: [byte(1), 2, 3, 4, 5] }) == '{"ba":[1,2,3,4,5]}' +} diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 428e4a67fa..e2c29d2b80 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -79,7 +79,6 @@ pub fn pref_arch_to_table_language(pref_arch pref.Arch) Language { // * Table.type_kind(typ) not TypeSymbol.kind. // Each TypeSymbol is entered into `Table.types`. // See also: Table.sym. - [minify] pub struct TypeSymbol { pub: diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index 80aace3dc8..7e7c1e8fd3 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -7,16 +7,21 @@ import v.ast import v.util import strings -// TODO replace with comptime code generation. -// TODO remove cJSON dependency. -// OLD: User decode_User(string js) { -// now it's +// TODO: replace with comptime code generation. +// TODO: remove cJSON dependency. + +// Old: +// `User decode_User(string js) {` +// now it's: +// ``` // User decode_User(cJSON* root) { -// User res; -// res.name = decode_string(js_get(root, "name")); -// res.profile = decode_Profile(js_get(root, "profile")); -// return res; +// User res; +// res.name = decode_string(js_get(root, "name")); +// res.profile = decode_Profile(js_get(root, "profile")); +// return res; // } +// ``` + // Codegen json_decode/encode funcs fn (mut g Gen) gen_json_for_type(typ ast.Type) { utyp := g.unwrap_generic(typ).set_nr_muls(0) @@ -40,10 +45,9 @@ fn (mut g Gen) gen_jsons() { sym := g.table.sym(utyp) styp := g.typ(utyp) g.register_optional(utyp) - // println('gen_json_for_type($sym.name)') // decode_TYPE funcs receive an actual cJSON* object to decode // cJSON_Parse(str) call is added by the compiler - // Code gen decoder + // Codegen decoder dec_fn_name := js_dec_name(styp) dec_fn_dec := '${option_name}_$styp ${dec_fn_name}(cJSON* root)' @@ -101,7 +105,7 @@ $dec_fn_dec { } ') g.json_forward_decls.writeln('$dec_fn_dec;') - // Code gen encoder + // Codegen encoder // encode_TYPE funcs receive an object to encode enc_fn_name := js_enc_name(styp) enc_fn_dec := 'cJSON* ${enc_fn_name}($styp val)' @@ -116,7 +120,6 @@ $enc_fn_dec { g.gen_json_for_type(value_type) dec.writeln(g.decode_array(value_type)) enc.writeln(g.encode_array(value_type)) - // enc += g.encode_array(t) } else if sym.kind == .map { // Handle maps m := sym.info as ast.Map @@ -156,7 +159,6 @@ $enc_fn_dec { g.gen_struct_enc_dec(sym.info, styp, mut enc, mut dec) } // cJSON_delete - // p.cgen.fns << '$dec return opt_ok(res); \n}' dec.writeln('\t${option_name}_$styp ret;') dec.writeln('\topt_ok2(&res, ($option_name*)&ret, sizeof(res));') dec.writeln('\treturn ret;\n}') @@ -396,17 +398,41 @@ fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc s if is_js_prim(field_type) { tmp := g.new_tmp_var() gen_js_get(styp, tmp, name, mut dec, is_required) + if field.has_default_expr { + dec.writeln('\tif (jsonroot_$tmp) {') + } dec.writeln('\tres.${c_name(field.name)} = $dec_name (jsonroot_$tmp);') + if field.has_default_expr { + dec.writeln('\t} else {') + dec.writeln('\tres.${c_name(field.name)} = ${g.expr_string(field.default_expr)};') + dec.writeln('\t}') + } } else if field_sym.kind == .enum_ { tmp := g.new_tmp_var() gen_js_get(styp, tmp, name, mut dec, is_required) + if field.has_default_expr { + dec.writeln('\tif (jsonroot_$tmp) {') + } dec.writeln('\tres.${c_name(field.name)} = json__decode_u64(jsonroot_$tmp);') + if field.has_default_expr { + dec.writeln('\t} else {') + dec.writeln('\tres.${c_name(field.name)} = ${g.expr_string(field.default_expr)};') + dec.writeln('\t}') + } } else if field_sym.name == 'time.Time' { // time struct requires special treatment // it has to be decoded from a unix timestamp number tmp := g.new_tmp_var() gen_js_get(styp, tmp, name, mut dec, is_required) + if field.has_default_expr { + dec.writeln('\tif (jsonroot_$tmp) {') + } dec.writeln('\tres.${c_name(field.name)} = time__unix(json__decode_u64(jsonroot_$tmp));') + if field.has_default_expr { + dec.writeln('\t} else {') + dec.writeln('\tres.${c_name(field.name)} = ${g.expr_string(field.default_expr)};') + dec.writeln('\t}') + } } else if field_sym.kind == .alias { alias := field_sym.info as ast.Alias parent_type := g.typ(alias.parent_type) @@ -414,17 +440,41 @@ fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc s if is_js_prim(parent_type) { tmp := g.new_tmp_var() gen_js_get(styp, tmp, name, mut dec, is_required) + if field.has_default_expr { + dec.writeln('\tif (jsonroot_$tmp) {') + } dec.writeln('\tres.${c_name(field.name)} = $parent_dec_name (jsonroot_$tmp);') + if field.has_default_expr { + dec.writeln('\t} else {') + dec.writeln('\tres.${c_name(field.name)} = ${g.expr_string(field.default_expr)};') + dec.writeln('\t}') + } } else { g.gen_json_for_type(field.typ) tmp := g.new_tmp_var() gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required) + if field.has_default_expr { + dec.writeln('\tif (jsonroot_$tmp) {') + } dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + if field.has_default_expr { + dec.writeln('\t} else {') + dec.writeln('\tres.${c_name(field.name)} = ${g.expr_string(field.default_expr)};') + dec.writeln('\t}') + } } } else { tmp := g.new_tmp_var() gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required) + if field.has_default_expr { + dec.writeln('\tif (jsonroot_$tmp) {') + } dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + if field.has_default_expr { + dec.writeln('\t} else {') + dec.writeln('\tres.${c_name(field.name)} = ${g.expr_string(field.default_expr)};') + dec.writeln('\t}') + } } } // Encoding @@ -453,7 +503,7 @@ fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc s } fn gen_js_get(styp string, tmp string, name string, mut dec strings.Builder, is_required bool) { - dec.writeln('\tcJSON *jsonroot_$tmp = js_get(root,"$name");') + dec.writeln('\tcJSON *jsonroot_$tmp = js_get(root, "$name");') if is_required { dec.writeln('\tif(jsonroot_$tmp == 0) {') dec.writeln('\t\treturn (${option_name}_$styp){ .state = 2, .err = _v_error(_SLIT("expected field \'$name\' is missing")), .data = {0} };') diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 6502be94f6..c36f59a585 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -40,11 +40,6 @@ fn (mut p Parser) struct_decl() ast.StructDecl { return ast.StructDecl{} } mut name := p.check_name() - // defer { - // if name.contains('App') { - // println('end of struct decl $name') - // } - // } if name.len == 1 && name[0].is_capital() { p.error_with_pos('single letter capital names are reserved for generic template types.', name_pos) @@ -328,9 +323,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { p.error_with_pos('invalid recursive struct `$orig_name`', name_pos) return ast.StructDecl{} } - mut ret := 0 - // println('reg type symbol $name mod=$p.mod') - ret = p.table.register_sym(t) + mut ret := p.table.register_sym(t) // allow duplicate c struct declarations if ret == -1 && language != .c { p.error_with_pos('cannot register struct `$name`, another type with this name exists',