Skip to content

Commit c99fded

Browse files
committed
json2 vs json: decoder2 can't convert float to u64 as json d... (fixes #25426)
1 parent d889526 commit c99fded

2 files changed

Lines changed: 58 additions & 8 deletions

File tree

vlib/x/json2/decode.v

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,35 @@ fn (mut decoder Decoder) decode_enum[T](mut val T) ! {
897897
decoder.decode_error('Expected number or string value for enum, got: ${enum_info.value_kind}')!
898898
}
899899

900+
@[inline]
901+
fn number_string_is_real(str string) bool {
902+
for ch in str {
903+
if ch == `.` || ch == `e` || ch == `E` {
904+
return true
905+
}
906+
}
907+
return false
908+
}
909+
910+
// Keep compatibility with the legacy json module, which truncates real-number
911+
// tokens when decoding into 64-bit integer targets.
912+
fn decode_i64_from_number_string(str string) !i64 {
913+
if number_string_is_real(str) {
914+
return i64(strconv.atof_quick(str))
915+
}
916+
return strconv.atoi64(str)!
917+
}
918+
919+
fn decode_u64_from_number_string(str string) !u64 {
920+
if number_string_is_real(str) {
921+
if str.len > 0 && str[0] == `-` {
922+
return strconv.atou64(str)!
923+
}
924+
return u64(strconv.atof_quick(str))
925+
}
926+
return strconv.atou64(str)!
927+
}
928+
900929
// use pointer instead of mut so enum cast works
901930
@[unsafe]
902931
fn (mut decoder Decoder) decode_number[T](val &T) ! {
@@ -906,14 +935,14 @@ fn (mut decoder Decoder) decode_number[T](val &T) ! {
906935
i8 { *val = strconv.atoi8(str)! }
907936
i16 { *val = strconv.atoi16(str)! }
908937
i32 { *val = strconv.atoi32(str)! }
909-
i64 { *val = strconv.atoi64(str)! }
938+
i64 { *val = decode_i64_from_number_string(str)! }
910939
u8 { *val = strconv.atou8(str)! }
911940
u16 { *val = strconv.atou16(str)! }
912941
u32 { *val = strconv.atou32(str)! }
913-
u64 { *val = strconv.atou64(str)! }
942+
u64 { *val = decode_u64_from_number_string(str)! }
914943
int { *val = strconv.atoi(str)! }
915-
isize { *val = isize(strconv.atoi64(str)!) }
916-
usize { *val = usize(strconv.atou64(str)!) }
944+
isize { *val = isize(decode_i64_from_number_string(str)!) }
945+
usize { *val = usize(decode_u64_from_number_string(str)!) }
917946
f32 { *val = f32(strconv.atof_quick(str)) }
918947
f64 { *val = strconv.atof_quick(str) }
919948
$else { return error('`decode_number` can not decode ${T.name} type') }
@@ -936,21 +965,21 @@ fn (mut decoder Decoder) decode_number_from_string[T]() !T {
936965
} $else $if T.unaliased_typ is i32 {
937966
return T(strconv.atoi32(str)!)
938967
} $else $if T.unaliased_typ is i64 {
939-
return T(strconv.atoi64(str)!)
968+
return T(decode_i64_from_number_string(str)!)
940969
} $else $if T.unaliased_typ is u8 {
941970
return T(strconv.atou8(str)!)
942971
} $else $if T.unaliased_typ is u16 {
943972
return T(strconv.atou16(str)!)
944973
} $else $if T.unaliased_typ is u32 {
945974
return T(strconv.atou32(str)!)
946975
} $else $if T.unaliased_typ is u64 {
947-
return T(strconv.atou64(str)!)
976+
return T(decode_u64_from_number_string(str)!)
948977
} $else $if T.unaliased_typ is int {
949978
return T(strconv.atoi(str)!)
950979
} $else $if T.unaliased_typ is isize {
951-
return T(isize(strconv.atoi64(str)!))
980+
return T(isize(decode_i64_from_number_string(str)!))
952981
} $else $if T.unaliased_typ is usize {
953-
return T(usize(strconv.atou64(str)!))
982+
return T(usize(decode_u64_from_number_string(str)!))
954983
} $else $if T.unaliased_typ is f32 {
955984
return T(f32(strconv.atof_quick(str)))
956985
} $else $if T.unaliased_typ is f64 {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import json as old_json
2+
import x.json2 as json
3+
4+
struct Legacy64BitNumbers {
5+
signed i64
6+
unsigned u64
7+
}
8+
9+
fn test_decode_float_numbers_into_64bit_integer_fields_matches_json_module() {
10+
payload := '{"signed": -1756811041916.0, "unsigned": 1756811041916.0}'
11+
expected := old_json.decode(Legacy64BitNumbers, payload)!
12+
actual := json.decode[Legacy64BitNumbers](payload)!
13+
assert actual == expected
14+
}
15+
16+
fn test_decode_float_strings_into_64bit_integer_fields_in_default_mode() {
17+
payload := '{"signed": "-1756811041916.0", "unsigned": "1756811041916.0"}'
18+
actual := json.decode[Legacy64BitNumbers](payload)!
19+
assert actual.signed == i64(-1756811041916)
20+
assert actual.unsigned == u64(1756811041916)
21+
}

0 commit comments

Comments
 (0)