Skip to content

Commit 703eca8

Browse files
authored
x.json2: cleanup code, add more tests (fix #26033) (#26064)
1 parent 3f279cc commit 703eca8

File tree

2 files changed

+219
-81
lines changed

2 files changed

+219
-81
lines changed

vlib/x/json2/decode.v

Lines changed: 13 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -823,68 +823,6 @@ fn create_value_from_optional[T](val ?T) ?T {
823823
return T{}
824824
}
825825

826-
fn get_number_max[T](num T) T {
827-
$if num is i8 {
828-
return max_i8
829-
} $else $if num is i16 {
830-
return max_i16
831-
} $else $if num is i32 {
832-
return max_i32
833-
} $else $if num is i64 {
834-
return max_i64
835-
} $else $if num is u8 {
836-
return max_u8
837-
} $else $if num is u16 {
838-
return max_u16
839-
} $else $if num is u32 {
840-
return max_u32
841-
} $else $if num is u64 {
842-
return max_u64
843-
} $else $if num is int {
844-
return max_int
845-
}
846-
return 0
847-
}
848-
849-
fn get_number_min[T](num T) T {
850-
$if num is i8 {
851-
return min_i8
852-
} $else $if num is i16 {
853-
return min_i16
854-
} $else $if num is i32 {
855-
return min_i32
856-
} $else $if num is i64 {
857-
return min_i64
858-
} $else $if num is u8 {
859-
return min_u8
860-
} $else $if num is u16 {
861-
return min_u16
862-
} $else $if num is u32 {
863-
return min_u32
864-
} $else $if num is u64 {
865-
return min_u64
866-
} $else $if num is int {
867-
return min_int
868-
}
869-
return 0
870-
}
871-
872-
fn get_number_digits[T](num T) int {
873-
return $if T.unaliased_typ is i8 || T.unaliased_typ is u8 {
874-
3
875-
} $else $if T.unaliased_typ is i16 || T.unaliased_typ is u16 {
876-
5
877-
} $else $if T.unaliased_typ is i32 || T.unaliased_typ is u32 || T.unaliased_typ is int {
878-
10
879-
} $else $if T.unaliased_typ is i64 {
880-
19
881-
} $else $if T.unaliased_typ is u64 {
882-
20
883-
} $else {
884-
0
885-
}
886-
}
887-
888826
fn (mut decoder Decoder) decode_enum[T](mut val T) ! {
889827
enum_info := decoder.current_node.value
890828

@@ -927,22 +865,18 @@ fn (mut decoder Decoder) decode_number[T](val &T) ! {
927865
}
928866
}
929867

930-
$if T.unaliased_typ is $float {
931-
*val = T(strconv.atof_quick(decoder.json[number_info.position..number_info.position +
932-
number_info.length]))
933-
} $else {
934-
str := decoder.json[number_info.position..number_info.position + number_info.length]
935-
$match T.unaliased_typ {
936-
i8 { *val = strconv.atoi8(str)! }
937-
i16 { *val = strconv.atoi16(str)! }
938-
i32 { *val = strconv.atoi32(str)! }
939-
i64 { *val = strconv.atoi64(str)! }
940-
u8 { *val = strconv.atou8(str)! }
941-
u16 { *val = strconv.atou16(str)! }
942-
u32 { *val = strconv.atou32(str)! }
943-
u64 { *val = strconv.atou64(str)! }
944-
int { *val = strconv.atoi(str)! }
945-
$else { return error('`decode_number` can not decode ${T.name} type') }
946-
}
868+
str := decoder.json[number_info.position..number_info.position + number_info.length]
869+
$match T.unaliased_typ {
870+
i8 { *val = strconv.atoi8(str)! }
871+
i16 { *val = strconv.atoi16(str)! }
872+
i32 { *val = strconv.atoi32(str)! }
873+
i64 { *val = strconv.atoi64(str)! }
874+
u8 { *val = strconv.atou8(str)! }
875+
u16 { *val = strconv.atou16(str)! }
876+
u32 { *val = strconv.atou32(str)! }
877+
u64 { *val = strconv.atou64(str)! }
878+
int { *val = strconv.atoi(str)! }
879+
$float { *val = T(strconv.atof_quick(str)) }
880+
$else { return error('`decode_number` can not decode ${T.name} type') }
947881
}
948882
}

vlib/x/json2/tests/decode_struct_test.v

Lines changed: 206 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import x.json2 as json
22
import time
3+
import math
34

45
const fixed_time = time.new(
56
year: 2022
@@ -74,19 +75,222 @@ fn test_option_types() {
7475
}
7576
}
7677

78+
// Test structure for basic number types
79+
struct JsonNumbers {
80+
val_i8 i8
81+
val_i16 i16
82+
val_i32 i32
83+
val_i64 i64
84+
val_u8 u8
85+
val_u16 u16
86+
val_u32 u32
87+
val_u64 u64
88+
val_int int
89+
val_f32 f32
90+
val_f64 f64
91+
}
92+
93+
// Test structure for float precision
94+
struct JsonFloats {
95+
val_f32 f32
96+
val_f64 f64
97+
}
98+
99+
// Test basic functionality
77100
struct JsonU8 {
78101
val1 u8
79102
val2 u8
80103
}
81104

82-
fn test_u8_vals() {
105+
fn test_u8_values() {
83106
x := JsonU8{
84107
val1: 58
85108
val2: 58
86109
}
87110

88111
str := json.encode(x)
89-
90112
y := json.decode[JsonU8](str) or { panic(err) }
91113
assert x == y
114+
println('✓ Basic u8 test passed')
115+
}
116+
117+
fn test_all_number_types() {
118+
// Test normal range values
119+
x := JsonNumbers{
120+
val_i8: -100
121+
val_i16: -1000
122+
val_i32: -100000
123+
val_i64: -1000000000
124+
val_u8: 58
125+
val_u16: 2000
126+
val_u32: 200000
127+
val_u64: 2000000000
128+
val_int: 300000
129+
val_f32: 3.14
130+
val_f64: 2.71828
131+
}
132+
133+
str := json.encode(x)
134+
y := json.decode[JsonNumbers](str) or { panic('Decoding failed: ${err}') }
135+
136+
assert x.val_i8 == y.val_i8
137+
assert x.val_i16 == y.val_i16
138+
assert x.val_i32 == y.val_i32
139+
assert x.val_i64 == y.val_i64
140+
assert x.val_u8 == y.val_u8
141+
assert x.val_u16 == y.val_u16
142+
assert x.val_u32 == y.val_u32
143+
assert x.val_u64 == y.val_u64
144+
assert x.val_int == y.val_int
145+
assert math.alike(x.val_f32, y.val_f32)
146+
assert math.alike(x.val_f64, y.val_f64)
147+
148+
println('✓ All number types test passed')
149+
}
150+
151+
fn test_number_boundaries() {
152+
// Test minimum and maximum values for various types
153+
test_cases := [
154+
json.encode(JsonU8{ val1: 0, val2: 255 }),
155+
json.encode(JsonU8{ val1: u8(128), val2: u8(255) }),
156+
'{"min_i8": -128, "max_i8": 127}',
157+
'{"min_u8": 0, "max_u8": 255}',
158+
'{"zero": 0}',
159+
]!
160+
161+
for case in test_cases {
162+
result := json.decode[JsonU8](case)!
163+
assert result.val1 >= 0 && result.val1 <= 255
164+
assert result.val2 >= 0 && result.val2 <= 255
165+
}
166+
println('✓ Boundary values test passed')
167+
}
168+
169+
fn test_fake_numbers() {
170+
// Test quoted numbers (fake numbers)
171+
fake_number_cases := [
172+
'{"val1": "123", "val2": "45"}', // Fully quoted
173+
'{"val1": 123, "val2": "45"}', // Mixed format
174+
'{"val1": "255", "val2": 0}', // Boundary value as string
175+
]!
176+
177+
for case in fake_number_cases {
178+
result := json.decode[JsonU8](case) or {
179+
panic('Fake number decoding failed: ${err}, input: ${case}')
180+
}
181+
assert result.val1 >= 0 && result.val1 <= 255
182+
assert result.val2 >= 0 && result.val2 <= 255
183+
}
184+
println('✓ Fake numbers (quoted numbers) test passed')
185+
}
186+
187+
fn test_error_conditions() {
188+
// Test invalid inputs that should trigger errors
189+
invalid_cases := [
190+
'{"val1": "abc", "val2": 58}', // Non-numeric string
191+
'{"val1": "", "val2": 58}', // Empty string
192+
'{"val1": 300, "val2": 58}', // Out of u8 range (300 > 255)
193+
'{"val1": -1, "val2": 58}', // Negative value for u8
194+
'{"val1": 123.45, "val2": 58}', // Float for integer type
195+
]!
196+
197+
mut error_count := 0
198+
for case in invalid_cases {
199+
result := json.decode[JsonU8](case) or {
200+
error_count++
201+
continue // Expected failure, error handling works correctly
202+
}
203+
// If we reach here, it means decoding succeeded when it should have failed
204+
panic('Expected decoding to fail but succeeded: ${case}')
205+
}
206+
assert error_count > 0 // Ensure we caught some errors
207+
println('✓ Error handling test passed')
208+
}
209+
210+
fn test_float_precision() {
211+
// Test floating point precision and scientific notation
212+
test_cases := [
213+
JsonFloats{
214+
val_f32: 3.14159265
215+
val_f64: 2.718281828459045
216+
},
217+
JsonFloats{
218+
val_f32: 1.0e10
219+
val_f64: 1.0e-10
220+
},
221+
JsonFloats{
222+
val_f32: -123.456
223+
val_f64: -789.012
224+
},
225+
JsonFloats{
226+
val_f32: 0.0
227+
val_f64: 0.0
228+
},
229+
]!
230+
231+
for x in test_cases {
232+
str := json.encode(x)
233+
y := json.decode[JsonFloats](str) or { panic('Float decoding failed: ${err}') }
234+
235+
// Use relative error for assertions
236+
assert math.alike(y.val_f32, x.val_f32)
237+
assert math.alike(y.val_f64, x.val_f64)
238+
}
239+
println('✓ Float precision test passed')
240+
}
241+
242+
fn test_performance_large_scale() {
243+
// Performance test: decode large numbers of values
244+
mut total := u64(0)
245+
246+
for i in 0 .. 1000 {
247+
x := JsonU8{
248+
val1: u8(i % 256)
249+
val2: u8((i * 7) % 256)
250+
}
251+
str := json.encode(x)
252+
y := json.decode[JsonU8](str) or { panic('Performance test decoding failed: ${err}') }
253+
total += y.val1 + y.val2
254+
}
255+
256+
assert total > 0 // Ensure the loop executed
257+
println('✓ Performance test passed')
258+
}
259+
260+
fn test_special_float_values() {
261+
// Test special float values
262+
263+
// Json does not support nan, +-inf yet
264+
assert json.encode(math.nan()) == 'nan'
265+
assert json.encode(math.inf(1)) == '+inf'
266+
assert json.encode(math.inf(-1)) == '-inf'
267+
268+
println('✓ Special float values test passed')
269+
}
270+
271+
fn test_json_format_consistency() {
272+
// Test that encoding and decoding preserves data integrity
273+
original := JsonNumbers{
274+
val_i8: 100
275+
val_i16: 1000
276+
val_i32: 100000
277+
val_i64: 1000000000
278+
val_u8: 33
279+
val_u16: 2000
280+
val_u32: 200000
281+
val_u64: 2000000000
282+
val_int: 300000
283+
val_f32: 3.14
284+
val_f64: 2.71828
285+
}
286+
287+
// Encode and decode multiple times
288+
str1 := json.encode(original)
289+
decoded1 := json.decode[JsonNumbers](str1) or { panic('First decode failed: ${err}') }
290+
str2 := json.encode(decoded1)
291+
decoded2 := json.decode[JsonNumbers](str2) or { panic('Second decode failed: ${err}') }
292+
293+
// All versions should be equivalent
294+
assert decoded1 == decoded2
295+
println('✓ JSON format consistency test passed')
92296
}

0 commit comments

Comments
 (0)