From 9970ecb8359e59127afad499fa3d3b955a4d38af Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 24 Apr 2026 01:42:12 +0300 Subject: [PATCH] json: fix json.decode does not parse time into Struct (fixes #15610) --- vlib/json/README.md | 3 +++ vlib/json/json_primitives.c.v | 18 ++++++++++++++++++ vlib/json/tests/json_test.v | 5 +++++ vlib/v/gen/c/json.v | 17 +++++++++++------ 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/vlib/json/README.md b/vlib/json/README.md index 5e46e97dc..daf62a8b4 100644 --- a/vlib/json/README.md +++ b/vlib/json/README.md @@ -7,6 +7,9 @@ For more details, see also the Large `i64` and `u64` values are encoded as exact decimal JSON numbers, and `json.decode` preserves those integer values when reading decimal integer input. +Struct fields of type `time.Time` can be decoded from either a JSON number or +from a JSON string in ISO 8601, RFC 3339, or Unix-timestamp form. + ## Examples Here is an example of encoding and decoding a V struct with several fields. diff --git a/vlib/json/json_primitives.c.v b/vlib/json/json_primitives.c.v index 5f7eb57b4..a4b2f553d 100644 --- a/vlib/json/json_primitives.c.v +++ b/vlib/json/json_primitives.c.v @@ -5,6 +5,7 @@ module json import math import strconv +import time #flag -I @VEXEROOT/thirdparty/cJSON #flag @VEXEROOT/thirdparty/cJSON/cJSON.o @@ -205,6 +206,23 @@ fn decode_bool(root &C.cJSON) bool { return C.cJSON_IsTrue(root) } +@[markused] +fn decode_time(root &C.cJSON) !time.Time { + if isnil(root) || C.cJSON_IsNull(root) { + return time.Time{} + } + mut decoded_time := time.Time{} + if C.cJSON_IsString(root) { + decoded_time.from_json_string(decode_string(root))! + return decoded_time + } + if C.cJSON_IsNumber(root) { + decoded_time.from_json_number(json_print(root))! + return decoded_time + } + return error('expected time.Time to decode from a JSON string or number, got: ${json_print(root)}') +} + // /////////////////// @[markused] diff --git a/vlib/json/tests/json_test.v b/vlib/json/tests/json_test.v index ccf5a58fa..b571435d9 100644 --- a/vlib/json/tests/json_test.v +++ b/vlib/json/tests/json_test.v @@ -167,6 +167,11 @@ fn test_encode_decode_time() { // println(user2.reg_date) } +fn test_decode_time_string_into_struct_field() { + user := json.decode(User2, '{"age": 25, "reg_date": "2001-01-01"}')! + assert user.reg_date.str() == '2001-01-01 00:00:00' +} + fn (mut u User) foo() string { return json.encode(u) } diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index 74f1b3c2a..155e2a6c2 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -863,19 +863,24 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st 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 + // it can be decoded from either a JSON string or number tmp := g.new_tmp_var() gen_js_get(styp, tmp, name, mut dec, is_required) dec.writeln('\tif (jsonroot_${tmp}) {') + tmp_time_res := g.new_tmp_var() + dec.writeln('\t\t${result_name}_time__Time ${tmp_time_res} = json__decode_time(jsonroot_${tmp});') + dec.writeln('\t\tif (${tmp_time_res}.is_error) {') + dec.writeln('\t\t\treturn (${result_name}_${styp}){ .is_error = true, .err = ${tmp_time_res}.err, .data = {0} };') + dec.writeln('\t\t}') if field.typ.has_flag(.option) { - dec.writeln('\t\tif (!(cJSON_IsNull(jsonroot_${tmp}))) {\n') - dec.writeln('\t\t\t${prefix}${op}${c_name(field.name)}.state = 0;\n') + dec.writeln('\t\tif (!(cJSON_IsNull(jsonroot_${tmp}))) {') + dec.writeln('\t\t\t${prefix}${op}${c_name(field.name)}.state = 0;') tmp_time_var := g.new_tmp_var() - dec.writeln('\t\t\t${g.base_type(field.typ)} ${tmp_time_var} = time__unix(json__decode_u64(jsonroot_${tmp}));\n') + dec.writeln('\t\t\t${g.base_type(field.typ)} ${tmp_time_var} = *(time__Time*)${tmp_time_res}.data;') dec.writeln('\t\t\tbuiltin__vmemcpy(&${prefix}${op}${c_name(field.name)}.data, &${tmp_time_var}, sizeof(${g.base_type(field.typ)}));') - dec.writeln('\t\t}\n') + dec.writeln('\t\t}') } else { - dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = time__unix(json__decode_u64(jsonroot_${tmp}));') + dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = *(time__Time*)${tmp_time_res}.data;') if field.has_default_expr { dec.writeln('\t} else {') dec.writeln('\t\t${prefix}${op}${c_name(field.name)} = ${g.expr_string_opt(field.typ, -- 2.39.5