1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

json2: decode refactor/fix (#16588)

This commit is contained in:
Hitalo Souza 2022-12-05 11:58:44 -03:00 committed by GitHub
parent 50110d4c19
commit 5288c613ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 251 additions and 243 deletions

View File

@ -58,8 +58,8 @@ fn test_int() {
assert sample_data['f32'] or { 0 }.int() == 2
assert sample_data['f64'] or { 0 }.int() == 1
assert json.Any(true).int() == 1
assert json.Any('123').int() == 123
// invalid conversions
assert json.Any('123').int() == 0
assert sample_data['null'] or { 0 }.int() == 0
assert sample_data['arr'] or { 0 }.int() == 0
assert sample_data['obj'] or { 0 }.int() == 0
@ -72,8 +72,8 @@ fn test_i64() {
assert sample_data['f32'] or { 0 }.i64() == 2
assert sample_data['f64'] or { 0 }.i64() == 1
assert json.Any(true).i64() == 1
assert json.Any('123').i64() == 123
// invalid conversions
assert json.Any('123').i64() == 0
assert sample_data['null'] or { 0 }.i64() == 0
assert sample_data['arr'] or { 0 }.i64() == 0
assert sample_data['obj'] or { 0 }.i64() == 0

View File

@ -59,7 +59,7 @@ pub fn (err UnknownTokenError) msg() string {
}
struct Parser {
mut:
pub mut:
scanner &Scanner = unsafe { nil }
p_tok Token
tok Token

View File

@ -1,48 +1,48 @@
module json2
import x.json2 as json
fn test_raw_decode_string() {
str := raw_decode('"Hello!"')!
str := json.raw_decode('"Hello!"')!
assert str.str() == 'Hello!'
}
fn test_raw_decode_string_escape() {
jstr := raw_decode('"\u001b"')!
jstr := json.raw_decode('"\u001b"')!
str := jstr.str()
assert str.len == 1
assert str[0] == 27
}
fn test_raw_decode_number() {
num := raw_decode('123')!
num := json.raw_decode('123')!
assert num.int() == 123
}
fn test_raw_decode_array() {
raw_arr := raw_decode('["Foo", 1]')!
raw_arr := json.raw_decode('["Foo", 1]')!
arr := raw_arr.arr()
assert arr[0] or { 0 }.str() == 'Foo'
assert arr[1] or { 0 }.int() == 1
}
fn test_raw_decode_bool() {
bol := raw_decode('false')!
bol := json.raw_decode('false')!
assert bol.bool() == false
}
fn test_raw_decode_map() {
raw_mp := raw_decode('{"name":"Bob","age":20}')!
raw_mp := json.raw_decode('{"name":"Bob","age":20}')!
mp := raw_mp.as_map()
assert mp['name'] or { 0 }.str() == 'Bob'
assert mp['age'] or { 0 }.int() == 20
}
fn test_raw_decode_null() {
nul := raw_decode('null')!
assert nul is Null
nul := json.raw_decode('null')!
assert nul is json.Null
}
fn test_raw_decode_invalid() {
raw_decode('1z') or {
json.raw_decode('1z') or {
assert err.msg() == '[x.json2] invalid token `z` (0:17)'
return
}
@ -50,25 +50,25 @@ fn test_raw_decode_invalid() {
}
fn test_raw_decode_string_with_dollarsign() {
str := raw_decode(r'"Hello $world"')!
str := json.raw_decode(r'"Hello $world"')!
assert str.str() == r'Hello $world'
}
fn test_raw_decode_map_with_whitespaces() {
raw_mp := raw_decode(' \n\t{"name":"Bob","age":20}\n\t')!
raw_mp := json.raw_decode(' \n\t{"name":"Bob","age":20}\n\t')!
mp := raw_mp.as_map()
assert mp['name'] or { 0 }.str() == 'Bob'
assert mp['age'] or { 0 }.int() == 20
}
fn test_nested_array_object() {
mut parser := new_parser(r'[[[[[],[],[]]]],{"Test":{}},[[]]]', false)
mut parser := json.new_parser(r'[[[[[],[],[]]]],{"Test":{}},[[]]]', false)
decoded := parser.decode()!
assert parser.n_level == 0
}
fn test_raw_decode_map_invalid() {
raw_decode('{"name","Bob","age":20}') or {
json.raw_decode('{"name","Bob","age":20}') or {
assert err.msg() == '[x.json2] invalid token `comma`, expecting `colon` (0:5)'
return
}
@ -76,7 +76,7 @@ fn test_raw_decode_map_invalid() {
}
fn test_raw_decode_array_invalid() {
raw_decode('["Foo", 1,}') or {
json.raw_decode('["Foo", 1,}') or {
assert err.msg() == '[x.json2] invalid token `rcbr` (0:5)'
return
}

View File

@ -12,42 +12,11 @@ mut:
sx64 i64
}
pub fn (mut x IntegerValues) from_json(f json2.Any) {
fm := f.as_map()
if v := fm['ux8'] {
x.ux8 = u8(v.u64())
}
if v := fm['ux16'] {
x.ux16 = u16(v.u64())
}
if v := fm['ux32'] {
x.ux32 = u32(v.u64())
}
if v := fm['ux64'] {
x.ux64 = v.u64()
}
//
if v := fm['sx8'] {
x.sx8 = i8(v.i64())
}
if v := fm['sx16'] {
x.sx16 = i16(v.i64())
}
if v := fm['sx32'] {
x.sx32 = int(v.i64())
}
if v := fm['sx64'] {
x.sx64 = v.i64()
}
}
fn test_all_primitive_integer_types_are_encodable_and_decodable() {
f := IntegerValues{1, 2, 3, 4, -1, -2, -3, -4}
s := json2.encode[IntegerValues](f)
dump(s)
assert s == '{"ux8":1,"ux16":2,"ux32":3,"ux64":4,"sx8":-1,"sx16":-2,"sx32":-3,"sx64":-4}'
x := json2.decode[IntegerValues](s)!
dump(x)
assert x == f
println('done')
}

View File

@ -4,6 +4,7 @@
module json2
import strings
import time
// Decodes a JSON string into an `Any` type. Returns an option.
pub fn raw_decode(src string) !Any {
@ -19,9 +20,41 @@ pub fn fast_raw_decode(src string) !Any {
// decode is a generic function that decodes a JSON string into the target type.
pub fn decode[T](src string) !T {
res := raw_decode(src)!
res := raw_decode(src)!.as_map()
mut typ := T{}
typ.from_json(res)
$for field in T.fields {
$if field.typ is u8 {
typ.$(field.name) = u8(res[field.name]!.u64())
} $else $if field.typ is u16 {
typ.$(field.name) = u16(res[field.name]!.u64())
} $else $if field.typ is u32 {
typ.$(field.name) = u32(res[field.name]!.u64())
} $else $if field.typ is u64 {
typ.$(field.name) = res[field.name]!.u64()
} $else $if field.typ is int {
typ.$(field.name) = res[field.name]!.int()
} $else $if field.typ is i8 {
typ.$(field.name) = i8(res[field.name]!.i64())
} $else $if field.typ is i16 {
typ.$(field.name) = i16(res[field.name]!.i64())
} $else $if field.typ is i32 {
// typ.$(field.name) = res[field.name]!.i32()
} $else $if field.typ is i64 {
typ.$(field.name) = res[field.name]!.i64()
} $else $if field.typ is f32 {
typ.$(field.name) = res[field.name]!.f32()
} $else $if field.typ is f64 {
typ.$(field.name) = res[field.name]!.f64()
} $else $if field.typ is bool {
typ.$(field.name) = res[field.name]!.bool()
} $else $if field.typ is string {
typ.$(field.name) = res[field.name]!.str()
} $else $if field.typ is time.Time {
// typ.$(field.name) = res[field.name]!.str()
} $else {
return error("The type of `${field.name}` can't be decoded. Please open an issue at https://github.com/vlang/v/issues/new/choose")
}
}
return typ
}
@ -41,57 +74,133 @@ pub fn encode[T](val T) string {
// int uses `Any` as an integer.
pub fn (f Any) int() int {
match f {
int { return f }
i8, i16, i64, u8, u16, u32, u64, f32, f64, bool { return int(f) }
else { return 0 }
int {
return f
}
i8, i16, i64, u8, u16, u32, u64, f32, f64, bool {
return int(f)
}
string {
if f == 'false' || f == 'true' {
return int(f.bool())
}
return f.int()
}
else {
return 0
}
}
}
// i64 uses `Any` as a 64-bit integer.
pub fn (f Any) i64() i64 {
match f {
i64 { return f }
i8, i16, int, u8, u16, u32, u64, f32, f64, bool { return i64(f) }
else { return 0 }
i64 {
return f
}
i8, i16, int, u8, u16, u32, u64, f32, f64, bool {
return i64(f)
}
string {
if f == 'false' || f == 'true' {
return i64(f.bool())
}
return f.i64()
}
else {
return 0
}
}
}
// u64 uses `Any` as a 64-bit unsigned integer.
pub fn (f Any) u64() u64 {
match f {
u64 { return f }
u8, u16, u32, i8, i16, int, i64, f32, f64, bool { return u64(f) }
else { return 0 }
u64 {
return f
}
u8, u16, u32, i8, i16, int, i64, f32, f64, bool {
return u64(f)
}
string {
if f == 'false' || f == 'true' {
return u64(f.bool())
}
return f.u64()
}
else {
return 0
}
}
}
// f32 uses `Any` as a 32-bit float.
pub fn (f Any) f32() f32 {
match f {
f32 { return f }
bool, i8, i16, int, i64, u8, u16, u32, u64, f64 { return f32(f) }
else { return 0.0 }
f32 {
return f
}
bool, i8, i16, int, i64, u8, u16, u32, u64, f64 {
return f32(f)
}
string {
if f == 'false' || f == 'true' {
return f32(f.bool())
}
return f.f32()
}
else {
return 0.0
}
}
}
// f64 uses `Any` as a 64-bit float.
pub fn (f Any) f64() f64 {
match f {
f64 { return f }
i8, i16, int, i64, u8, u16, u32, u64, f32 { return f64(f) }
else { return 0.0 }
f64 {
return f
}
i8, i16, int, i64, u8, u16, u32, u64, f32 {
return f64(f)
}
string {
if f == 'false' || f == 'true' {
return f64(f.bool())
}
return f.f64()
}
else {
return 0.0
}
}
}
// bool uses `Any` as a bool.
pub fn (f Any) bool() bool {
match f {
bool { return f }
string { return f.bool() }
i8, i16, int, i64 { return i64(f) != 0 }
u8, u16, u32, u64 { return u64(f) != 0 }
f32, f64 { return f64(f) != 0.0 }
else { return false }
bool {
return f
}
string {
if f.len > 0 {
return f != '0' && f != '0.0' && f != 'false'
} else {
return false
}
}
i8, i16, int, i64 {
return i64(f) != 0
}
u8, u16, u32, u64 {
return u64(f) != 0
}
f32, f64 {
return f64(f) != 0.0
}
else {
return false
}
}
}

View File

@ -44,14 +44,6 @@ fn (e Employee) to_json() string {
return mp.str()
}
fn (mut e Employee) from_json(any json.Any) {
mp := any.as_map()
e.name = mp['name'] or { json.Any('') }.str()
e.age = mp['age'] or { json.Any(0) }.int()
e.salary = mp['salary'] or { json.Any(0) }.f32()
e.title = unsafe { JobTitle(mp['title'] or { json.Any(0) }.int()) }
}
// ! BUGFIX
// fn test_simplegg() {
// // x := EmployeeOp{'Peter', 28, 95000.5, .worker}
@ -102,92 +94,34 @@ fn test_character_unescape() {
assert lines['slash'] or { 0 }.str() == '/dev/null'
}
fn (mut u User2) from_json(an json.Any) {
mp := an.as_map()
mut js_field_name := ''
$for field in User.fields {
js_field_name = field.name
for attr in field.attrs {
if attr.starts_with('json:') {
js_field_name = attr.all_after('json:').trim_left(' ')
break
}
}
match field.name {
'age' { u.age = mp[js_field_name] or { 0 }.int() }
'nums' { u.nums = mp[js_field_name] or { 0 }.arr().map(it.int()) }
else {}
}
}
struct MultTypeTest[T] {
mut:
val T
}
struct User2 {
pub mut:
age int
nums []int
reg_date time.Time
}
// NOTE - This can substitute a lot of others tests
fn test_bool_decode() {
assert json.decode[MultTypeTest[bool]]('{"val": ""}')!.val == false
assert json.decode[MultTypeTest[bool]]('{"val": "0"}')!.val == false
assert json.decode[MultTypeTest[bool]]('{"val": "1"}')!.val == true
assert json.decode[MultTypeTest[bool]]('{"val": "2"}')!.val == true
assert json.decode[MultTypeTest[bool]]('{"val": 0}')!.val == false
assert json.decode[MultTypeTest[bool]]('{"val": 1}')!.val == true
assert json.decode[MultTypeTest[bool]]('{"val": 2}')!.val == true
assert json.decode[MultTypeTest[bool]]('{"val": "true"}')!.val == true
assert json.decode[MultTypeTest[bool]]('{"val": "false"}')!.val == false
assert json.decode[MultTypeTest[bool]]('{"val": true}')!.val == true
assert json.decode[MultTypeTest[bool]]('{"val": false}')!.val == false
struct User {
pub mut:
age int
nums []int
last_name string [json: lastName]
is_registered bool [json: IsRegistered]
typ int [json: 'type']
pets string [json: 'pet_animals'; raw]
}
fn (mut u User) from_json(an json.Any) {
mp := an.as_map()
mut js_field_name := ''
$for field in User.fields {
// FIXME: C error when initializing js_field_name inside comptime for
js_field_name = field.name
for attr in field.attrs {
if attr.starts_with('json:') {
js_field_name = attr.all_after('json:').trim_left(' ')
break
}
}
match field.name {
'age' { u.age = mp[js_field_name] or { 0 }.int() }
'nums' { u.nums = mp[js_field_name] or { 0 }.arr().map(it.int()) }
'last_name' { u.last_name = mp[js_field_name] or { 0 }.str() }
'is_registered' { u.is_registered = mp[js_field_name] or { 0 }.bool() }
'typ' { u.typ = mp[js_field_name] or { 0 }.int() }
'pets' { u.pets = mp[js_field_name] or { 0 }.str() }
else {}
}
}
}
fn (u User) to_json() string {
// TODO: derive from field
mut mp := {
'age': json.Any(u.age)
}
mp['nums'] = u.nums.map(json.Any(it))
mp['lastName'] = json.Any(u.last_name)
mp['IsRegistered'] = json.Any(u.is_registered)
mp['type'] = json.Any(u.typ)
mp['pet_animals'] = json.Any(u.pets)
return mp.str()
}
struct Color {
pub mut:
space string
point string [raw]
}
fn (mut c Color) from_json(an json.Any) {
mp := an.as_map()
$for field in Color.fields {
match field.name {
'space' { c.space = mp[field.name] or { 0 }.str() }
'point' { c.point = mp[field.name] or { 0 }.str() }
else {}
}
}
assert json.decode[MultTypeTest[int]]('{"val": ""}')!.val == 0
assert json.decode[MultTypeTest[int]]('{"val": "0"}')!.val == 0
assert json.decode[MultTypeTest[int]]('{"val": "1"}')!.val == 1
assert json.decode[MultTypeTest[int]]('{"val": "2"}')!.val == 2
assert json.decode[MultTypeTest[int]]('{"val": 0}')!.val == 0
assert json.decode[MultTypeTest[int]]('{"val": 1}')!.val == 1
assert json.decode[MultTypeTest[int]]('{"val": 2}')!.val == 2
assert json.decode[MultTypeTest[int]]('{"val": "true"}')!.val == 1
assert json.decode[MultTypeTest[int]]('{"val": "false"}')!.val == 0
assert json.decode[MultTypeTest[int]]('{"val": true}')!.val == 1
assert json.decode[MultTypeTest[int]]('{"val": false}')!.val == 0
}

View File

@ -13,7 +13,7 @@ fn test_json_decode_with_optional_arg() {
}
fn print_info() !string {
dbconf := json.decode<DbConfig>(os.read_file('dbconf.json')!)!
dbconf := json.decode[DbConfig](os.read_file('dbconf.json')!)!
println(dbconf)
return '${dbconf}'
}

View File

@ -7,15 +7,15 @@ struct Some {
}
fn test_json_decode_with_sumtype() {
v1 := json.decode<Some>('{"t": ["string", "string2"]}')!
v1 := json.decode[Some]('{"t": ["string", "string2"]}')!
println(v1)
assert v1.t == Test(['string', 'string2'])
v2 := json.decode<Some>('{"t": [11, 22]}')!
v2 := json.decode[Some]('{"t": [11, 22]}')!
println(v2)
assert v2.t == Test([11, 22])
v3 := json.decode<Some>('{"t": [true, false]}')!
v3 := json.decode[Some]('{"t": [true, false]}')!
println(v3)
assert v3.t == Test([true, false])
}

View File

@ -1,5 +1,5 @@
import x.json2 as json
import time
// import time
enum JobTitle {
manager
@ -9,11 +9,11 @@ enum JobTitle {
struct Employee {
pub mut:
name string
age int
salary f32
title JobTitle
sub_employee SubEmployee
name string
age int
salary f32
// title JobTitle //! FIXME - decode
// sub_employee SubEmployee //! FIXME - decode
}
struct SubEmployee {
@ -21,37 +21,30 @@ pub mut:
name string
age int
salary f32
title JobTitle
// title JobTitle //! FIXME - decode
}
fn test_simple() {
sub_employee := SubEmployee{
name: 'João'
}
x := Employee{'Peter', 28, 95000.5, .worker, sub_employee}
x := Employee{'Peter', 28, 95000.5}
s := json.encode[Employee](x)
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2,"sub_employee":{"name":"João","age":0,"salary":0.0,"title":0}}'
// y := json.decode<Employee>(s) or {
// println(err)
// assert false
// return
// }
// assert y.name == 'Peter'
// assert y.age == 28
// assert y.salary == 95000.5
// assert y.title == .worker
// x := Employee{'Peter', 28, 95000.5, .worker}
// s := json.encode<Employee>(x)
// assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
// // y := json.decode<Employee>(s) or {
// // println(err)
// // assert false
// // return
// // }
// // assert y.name == 'Peter'
// // assert y.age == 28
// // assert y.salary == 95000.5
// // assert y.title == .worker
assert s == '{"name":"Peter","age":28,"salary":95000.5}'
y := json.decode[Employee](s) or {
println(err)
assert false
return
}
assert y.name == 'Peter'
assert y.age == 28
assert y.salary == 95000.5
// assert y.title == .worker //! FIXME
// assert y.sub_employee.name == 'Peter'
// assert y.sub_employee.age == 0
// assert y.sub_employee.salary == 0.0
// assert y.sub_employee.title == .worker //! FIXME
}
// const currency_id = 'cconst'
@ -100,11 +93,11 @@ fn test_encode_user() {
assert usr.foo() == expected
}
// struct Color {
// pub mut:
// space string
// point string [raw]
// }
struct Color {
pub mut:
space string
point string [raw]
}
fn test_encode_map() {
expected := '{"one":1,"two":2,"three":3,"four":4}'
@ -147,3 +140,41 @@ struct StByteArray {
fn test_byte_array() {
assert json.encode(StByteArray{ ba: [byte(1), 2, 3, 4, 5] }) == '{"ba":[1,2,3,4,5]}'
}
struct Bar {
x string
}
fn bar[T](payload string) !Bar { // ?T doesn't work currently
result := json.decode[T](payload)!
return result
}
fn test_generic() {
result := bar[Bar]('{"x":"test"}') or { Bar{} }
assert result.x == 'test'
}
fn test_raw_json_field() {
color := json.decode[Color]('{"space": "YCbCr", "point": {"Y": 123}}') or {
assert false
Color{}
}
assert color.point == '{"Y":123}'
assert color.space == 'YCbCr'
}
struct Foo[T] {
pub:
name string
data T
}
fn test_generic_struct() {
foo_int := Foo[int]{'bar', 12}
foo_enc := json.encode(foo_int)
assert foo_enc == '{"name":"bar","data":12}'
foo_dec := json.decode[Foo[int]](foo_enc)!
assert foo_dec.name == 'bar'
assert foo_dec.data == 12
}

View File

@ -7,7 +7,7 @@ enum JobTitle {
worker
}
struct Employee {
pub struct Employee {
pub mut:
name string
age int
@ -17,7 +17,7 @@ pub mut:
const currency_id = 'cconst'
struct Price {
pub struct Price {
net f64
currency_id string [json: currencyId] = currency_id
}
@ -55,7 +55,7 @@ struct Item {
}
enum Animal {
dog Will be encoded as `0`
dog // Will be encoded as `0`
cat
}
@ -94,16 +94,6 @@ fn test_encode_decode_sumtype() {
assert (game.other[4] as time.Time).unix_time() == (dec.other[4] as time.Time).unix_time()
}
struct Bar {
x string
}
//! BUGFIX - .from_json(res)
fn test_generic() {
result := bar<Bar>('{"x":"test"}') or { Bar{} }
assert result.x == 'test'
}
struct User2 {
mut:
age int
@ -111,7 +101,7 @@ mut:
reg_date time.Time
}
User struct needs to be `pub mut` for now in order to access and manipulate values
// User struct needs to be `pub mut` for now in order to access and manipulate values
struct User {
pub mut:
age int
@ -155,15 +145,6 @@ pub mut:
point string [raw]
}
fn test_raw_json_field() {
color := json.decode<Color>('{"space": "YCbCr", "point": {"Y": 123}}') or {
assert false
Color{}
}
assert color.point == '{"Y":123}'
assert color.space == 'YCbCr'
}
//! FIX: returning 0
fn test_bad_raw_json_field() {
color := json.decode<Color>('{"space": "YCbCr"}') or {
@ -282,22 +263,6 @@ fn test_nested_type() {
}
}
struct Foo[T] {
pub:
name string
data T
}
//! BUGFIX - .from_json(res)
fn test_generic_struct() {
foo_int := Foo[int]{'bar', 12}
foo_enc := json.encode(foo_int)
assert foo_enc == '{"name":"bar","data":12}'
foo_dec := json.decode[Foo[int]](foo_enc)!
assert foo_dec.name == 'bar'
assert foo_dec.data == 12
}
//! BUGFIX - .from_json(res)
fn test_errors() {
invalid_array := fn () {