mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
toml: add generic automatic decoding and encoding of simple structs, when they don't implement custom methods (#17970)
This commit is contained in:
parent
d9ad6be5b0
commit
1bed0b5e68
2 changed files with 217 additions and 14 deletions
|
@ -1,13 +1,40 @@
|
||||||
import toml
|
import toml
|
||||||
|
|
||||||
enum JobTitle {
|
enum JobTitle {
|
||||||
manager
|
|
||||||
executive
|
|
||||||
worker
|
worker
|
||||||
|
executive
|
||||||
|
manager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Pet {
|
||||||
|
name string
|
||||||
|
nicknames []string
|
||||||
|
age u64
|
||||||
|
income int
|
||||||
|
height f32
|
||||||
|
has_furr bool
|
||||||
|
title JobTitle
|
||||||
|
address Address
|
||||||
|
// *¹ Currently it is only possible to decode a single nested struct generically.
|
||||||
|
// As soon as we decode another nested struct (e.g. within this struct, like `contact` below)
|
||||||
|
// or only one nested struct within another struct, it results in wrong values or errors.
|
||||||
|
// Related issue: https://github.com/vlang/v/issues/18110
|
||||||
|
// contact Contact
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Address {
|
||||||
|
street string
|
||||||
|
city string
|
||||||
|
}
|
||||||
|
|
||||||
|
// *¹
|
||||||
|
/*
|
||||||
|
struct Contact {
|
||||||
|
phone string
|
||||||
|
}*/
|
||||||
|
|
||||||
struct Employee {
|
struct Employee {
|
||||||
pub mut:
|
mut:
|
||||||
name string
|
name string
|
||||||
age int
|
age int
|
||||||
salary f32
|
salary f32
|
||||||
|
@ -15,13 +42,49 @@ pub mut:
|
||||||
title JobTitle
|
title JobTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Arrs {
|
||||||
|
strs []string
|
||||||
|
bools []bool
|
||||||
|
ints []int
|
||||||
|
i64s []i64
|
||||||
|
u64s []u64
|
||||||
|
f32s []f32
|
||||||
|
f64s []f64
|
||||||
|
dts []toml.DateTime
|
||||||
|
dates []toml.Date
|
||||||
|
times []toml.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_encode_and_decode() {
|
||||||
|
// *¹
|
||||||
|
// p := Pet{'Mr. Scratchy McEvilPaws', ['Freddy', 'Fred', 'Charles'], 8, -1, 0.8, true, .manager, Address{'1428 Elm Street', 'Springwood'}, Contact{'123-456-7890'}}
|
||||||
|
p := Pet{'Mr. Scratchy McEvilPaws', ['Freddy', 'Fred', 'Charles'], 8, -1, 0.8, true, .manager, Address{'1428 Elm Street', 'Springwood'}}
|
||||||
|
s := 'name = "Mr. Scratchy McEvilPaws"
|
||||||
|
nicknames = [
|
||||||
|
"Freddy",
|
||||||
|
"Fred",
|
||||||
|
"Charles"
|
||||||
|
]
|
||||||
|
age = 8
|
||||||
|
income = -1
|
||||||
|
height = 0.8
|
||||||
|
has_furr = true
|
||||||
|
title = 2
|
||||||
|
address = { street = "1428 Elm Street", city = "Springwood" }'
|
||||||
|
// contact = { phone = "123-456-7890" }' // *¹
|
||||||
|
|
||||||
|
assert toml.encode[Pet](p) == s
|
||||||
|
assert toml.decode[Pet](s)! == p
|
||||||
|
}
|
||||||
|
|
||||||
pub fn (e Employee) to_toml() string {
|
pub fn (e Employee) to_toml() string {
|
||||||
mut mp := map[string]toml.Any{}
|
mut mp := map[string]toml.Any{}
|
||||||
mp['name'] = toml.Any(e.name)
|
mp['name'] = toml.Any(e.name)
|
||||||
mp['age'] = toml.Any(e.age)
|
mp['age'] = toml.Any(e.age)
|
||||||
mp['salary'] = toml.Any(e.salary)
|
|
||||||
mp['is_human'] = toml.Any(e.is_human)
|
mp['is_human'] = toml.Any(e.is_human)
|
||||||
mp['title'] = toml.Any(int(e.title))
|
// Change some values to assert that the custom method is used instead of generic encoding.
|
||||||
|
mp['salary'] = toml.Any(f32(e.salary) + 5000.0)
|
||||||
|
mp['title'] = toml.Any(int(e.title) + 1)
|
||||||
return mp.to_toml()
|
return mp.to_toml()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,19 +92,20 @@ pub fn (mut e Employee) from_toml(any toml.Any) {
|
||||||
mp := any.as_map()
|
mp := any.as_map()
|
||||||
e.name = mp['name'] or { toml.Any('') }.string()
|
e.name = mp['name'] or { toml.Any('') }.string()
|
||||||
e.age = mp['age'] or { toml.Any(0) }.int()
|
e.age = mp['age'] or { toml.Any(0) }.int()
|
||||||
e.salary = mp['salary'] or { toml.Any(0) }.f32()
|
|
||||||
e.is_human = mp['is_human'] or { toml.Any(false) }.bool()
|
e.is_human = mp['is_human'] or { toml.Any(false) }.bool()
|
||||||
e.title = unsafe { JobTitle(mp['title'] or { toml.Any(0) }.int()) }
|
// Change some values to assert that the custom method is used instead of generic decoding.
|
||||||
|
e.salary = mp['salary'] or { toml.Any(0) }.f32() - 15000.0
|
||||||
|
e.title = unsafe { JobTitle(mp['title'] or { toml.Any(0) }.int() - 2) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_encode_and_decode() {
|
fn test_custom_encode_and_decode() {
|
||||||
x := Employee{'Peter', 28, 95000.5, true, .worker}
|
x := Employee{'Peter', 28, 95000.5, true, .executive}
|
||||||
s := toml.encode[Employee](x)
|
s := toml.encode[Employee](x)
|
||||||
eprintln('Employee x: ${s}')
|
eprintln('Employee x: ${s}')
|
||||||
assert s == r'name = "Peter"
|
assert s == r'name = "Peter"
|
||||||
age = 28
|
age = 28
|
||||||
salary = 95000.5
|
|
||||||
is_human = true
|
is_human = true
|
||||||
|
salary = 100000.5
|
||||||
title = 2'
|
title = 2'
|
||||||
|
|
||||||
y := toml.decode[Employee](s) or {
|
y := toml.decode[Employee](s) or {
|
||||||
|
@ -52,7 +116,64 @@ title = 2'
|
||||||
eprintln('Employee y: ${y}')
|
eprintln('Employee y: ${y}')
|
||||||
assert y.name == 'Peter'
|
assert y.name == 'Peter'
|
||||||
assert y.age == 28
|
assert y.age == 28
|
||||||
assert y.salary == 95000.5
|
assert y.salary == 85000.5
|
||||||
assert y.is_human == true
|
assert y.is_human == true
|
||||||
assert y.title == .worker
|
assert y.title == .worker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_array_encode_decode() {
|
||||||
|
a := Arrs{
|
||||||
|
strs: ['foo', 'bar']
|
||||||
|
bools: [true, false]
|
||||||
|
ints: [-1, 2]
|
||||||
|
i64s: [i64(-2)]
|
||||||
|
u64s: [u64(123)]
|
||||||
|
f32s: [f32(1.0), f32(2.5)]
|
||||||
|
f64s: [100000.5, -123.0]
|
||||||
|
dts: [toml.DateTime{'1979-05-27T07:32:00Z'}, toml.DateTime{'1979-05-27T07:32:00Z'}]
|
||||||
|
dates: [toml.Date{'1979-05-27'}, toml.Date{'2022-12-31'}]
|
||||||
|
times: [toml.Time{'07:32:59'}, toml.Time{'17:32:04'}]
|
||||||
|
}
|
||||||
|
|
||||||
|
s := 'strs = [
|
||||||
|
"foo",
|
||||||
|
"bar"
|
||||||
|
]
|
||||||
|
bools = [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
ints = [
|
||||||
|
-1,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
i64s = [
|
||||||
|
-2
|
||||||
|
]
|
||||||
|
u64s = [
|
||||||
|
123
|
||||||
|
]
|
||||||
|
f32s = [
|
||||||
|
1.0,
|
||||||
|
2.5
|
||||||
|
]
|
||||||
|
f64s = [
|
||||||
|
100000.5,
|
||||||
|
-123.0
|
||||||
|
]
|
||||||
|
dts = [
|
||||||
|
1979-05-27T07:32:00Z,
|
||||||
|
1979-05-27T07:32:00Z
|
||||||
|
]
|
||||||
|
dates = [
|
||||||
|
1979-05-27,
|
||||||
|
2022-12-31
|
||||||
|
]
|
||||||
|
times = [
|
||||||
|
07:32:59,
|
||||||
|
17:32:04
|
||||||
|
]'
|
||||||
|
|
||||||
|
assert toml.encode[Arrs](a) == s
|
||||||
|
assert toml.decode[Arrs](s)! == a
|
||||||
|
}
|
||||||
|
|
|
@ -13,17 +13,99 @@ pub struct Null {
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode decodes a TOML `string` into the target type `T`.
|
// decode decodes a TOML `string` into the target type `T`.
|
||||||
|
// If `T` has a custom `.from_toml()` method, it will be used instead of the default.
|
||||||
pub fn decode[T](toml_txt string) !T {
|
pub fn decode[T](toml_txt string) !T {
|
||||||
doc := parse_text(toml_txt)!
|
doc := parse_text(toml_txt)!
|
||||||
mut typ := T{}
|
mut typ := T{}
|
||||||
typ.from_toml(doc.to_any())
|
$for method in T.methods {
|
||||||
|
$if method.name == 'from_toml' {
|
||||||
|
typ.$method(doc.to_any())
|
||||||
|
return typ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decode_struct[T](doc.to_any(), mut typ)
|
||||||
return typ
|
return typ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_struct[T](doc Any, mut typ T) {
|
||||||
|
$for field in T.fields {
|
||||||
|
value := doc.value(field.name)
|
||||||
|
$if field.is_enum {
|
||||||
|
typ.$(field.name) = value.int()
|
||||||
|
} $else $if field.typ is string {
|
||||||
|
typ.$(field.name) = value.string()
|
||||||
|
} $else $if field.typ is bool {
|
||||||
|
typ.$(field.name) = value.bool()
|
||||||
|
} $else $if field.typ is int {
|
||||||
|
typ.$(field.name) = value.int()
|
||||||
|
} $else $if field.typ is i64 {
|
||||||
|
typ.$(field.name) = value.i64()
|
||||||
|
} $else $if field.typ is u64 {
|
||||||
|
typ.$(field.name) = value.u64()
|
||||||
|
} $else $if field.typ is f32 {
|
||||||
|
typ.$(field.name) = value.f32()
|
||||||
|
} $else $if field.typ is f64 {
|
||||||
|
typ.$(field.name) = value.f64()
|
||||||
|
} $else $if field.typ is DateTime {
|
||||||
|
typ.$(field.name) = value.datetime()
|
||||||
|
} $else $if field.typ is Date {
|
||||||
|
typ.$(field.name) = value.date()
|
||||||
|
} $else $if field.typ is Time {
|
||||||
|
typ.$(field.name) = value.time()
|
||||||
|
} $else $if field.is_array {
|
||||||
|
arr := value.array()
|
||||||
|
match typeof(typ.$(field.name)).name {
|
||||||
|
'[]string' { typ.$(field.name) = arr.as_strings() }
|
||||||
|
'[]int' { typ.$(field.name) = arr.map(it.int()) }
|
||||||
|
'[]i64' { typ.$(field.name) = arr.map(it.i64()) }
|
||||||
|
'[]u64' { typ.$(field.name) = arr.map(it.u64()) }
|
||||||
|
'[]f32' { typ.$(field.name) = arr.map(it.f32()) }
|
||||||
|
'[]f64' { typ.$(field.name) = arr.map(it.f64()) }
|
||||||
|
'[]bool' { typ.$(field.name) = arr.map(it.bool()) }
|
||||||
|
'[]toml.DateTime' { typ.$(field.name) = arr.map(it.datetime()) }
|
||||||
|
'[]toml.Date' { typ.$(field.name) = arr.map(it.date()) }
|
||||||
|
'[]toml.Time' { typ.$(field.name) = arr.map(it.time()) }
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
} $else $if field.is_struct {
|
||||||
|
mut s := typ.$(field.name)
|
||||||
|
decode_struct(value, mut s)
|
||||||
|
typ.$(field.name) = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// encode encodes the type `T` into a TOML string.
|
// encode encodes the type `T` into a TOML string.
|
||||||
// Currently encode expects the method `.to_toml()` exists on `T`.
|
// If `T` has a custom `.to_toml()` method, it will be used instead of the default.
|
||||||
pub fn encode[T](typ T) string {
|
pub fn encode[T](typ T) string {
|
||||||
return typ.to_toml()
|
$for method in T.methods {
|
||||||
|
$if method.name == 'to_toml' {
|
||||||
|
return typ.$method()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mp := encode_struct[T](typ)
|
||||||
|
return mp.to_toml()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_struct[T](typ T) map[string]Any {
|
||||||
|
mut mp := map[string]Any{}
|
||||||
|
$for field in T.fields {
|
||||||
|
value := typ.$(field.name)
|
||||||
|
$if field.is_enum {
|
||||||
|
mp[field.name] = Any(int(value))
|
||||||
|
} $else $if field.is_struct {
|
||||||
|
mp[field.name] = encode_struct(value)
|
||||||
|
} $else $if field.is_array {
|
||||||
|
mut arr := []Any{}
|
||||||
|
for v in value {
|
||||||
|
arr << Any(v)
|
||||||
|
}
|
||||||
|
mp[field.name] = arr
|
||||||
|
} $else {
|
||||||
|
mp[field.name] = Any(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mp
|
||||||
}
|
}
|
||||||
|
|
||||||
// DateTime is the representation of an RFC 3339 datetime string.
|
// DateTime is the representation of an RFC 3339 datetime string.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue