mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
x.json2.decoder: support sumtypes fully (#22694)
This commit is contained in:
parent
6e9a66dbf3
commit
54a6915f02
6 changed files with 214 additions and 63 deletions
|
@ -549,19 +549,6 @@ pub fn decode[T](val string) !T {
|
|||
return result
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T) !T {
|
||||
$if initialized_sumtype is $sumtype {
|
||||
$for v in initialized_sumtype.variants {
|
||||
if initialized_sumtype is v {
|
||||
mut val := initialized_sumtype
|
||||
decoder.decode_value(mut val)!
|
||||
return T(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
return initialized_sumtype
|
||||
}
|
||||
|
||||
// decode_value decodes a value from the JSON nodes.
|
||||
fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
$if T is $option {
|
||||
|
@ -620,38 +607,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
|||
val = string_buffer.bytestr()
|
||||
}
|
||||
} $else $if T.unaliased_typ is $sumtype {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
$for v in val.variants {
|
||||
if value_info.value_kind == .string_ {
|
||||
$if v.typ in [string, time.Time] {
|
||||
val = T(v)
|
||||
}
|
||||
} else if value_info.value_kind == .number {
|
||||
$if v.typ in [$float, $int, $enum] {
|
||||
val = T(v)
|
||||
}
|
||||
} else if value_info.value_kind == .boolean {
|
||||
$if v.typ is bool {
|
||||
val = T(v)
|
||||
}
|
||||
} else if value_info.value_kind == .object {
|
||||
$if v.typ is $map {
|
||||
val = T(v)
|
||||
} $else $if v.typ is $struct {
|
||||
// Will only be supported when json object has field "_type"
|
||||
error('cannot encode value with ${typeof(val).name} type')
|
||||
}
|
||||
} else if value_info.value_kind == .array {
|
||||
$if v.typ is $array {
|
||||
val = T(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
decoded_sumtype := decoder.get_decoded_sumtype_workaround(val)!
|
||||
unsafe {
|
||||
*val = decoded_sumtype
|
||||
}
|
||||
decoder.decode_sumtype(mut val)!
|
||||
} $else $if T.unaliased_typ is time.Time {
|
||||
time_info := decoder.current_node.value
|
||||
|
||||
|
@ -768,7 +724,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
|||
unsafe {
|
||||
val = vmemcmp(decoder.json.str + value_info.position, 'true'.str, 4) == 0
|
||||
}
|
||||
} $else $if T.unaliased_typ in [$int, $float, $enum] {
|
||||
} $else $if T.unaliased_typ in [$float, $int, $enum] {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
if value_info.value_kind == .number {
|
||||
|
@ -953,7 +909,7 @@ pub fn string_buffer_to_generic_number[T](result &T, data []u8) {
|
|||
} $else $if T.unaliased_typ is $float {
|
||||
mut is_negative := false
|
||||
mut decimal_seen := false
|
||||
mut decimal_divider := int(1)
|
||||
mut decimal_divider := T(1)
|
||||
|
||||
for ch in data {
|
||||
if ch == `-` {
|
||||
|
@ -965,13 +921,13 @@ pub fn string_buffer_to_generic_number[T](result &T, data []u8) {
|
|||
continue
|
||||
}
|
||||
|
||||
digit := T(ch - `0`)
|
||||
digit := T(ch - u8(`0`))
|
||||
|
||||
if decimal_seen {
|
||||
decimal_divider *= 10
|
||||
*result += T(digit / decimal_divider)
|
||||
} else {
|
||||
*result = *result * 10 + digit
|
||||
*result = T(*result * 10 + digit)
|
||||
}
|
||||
}
|
||||
if is_negative {
|
||||
|
|
117
vlib/x/json2/decoder2/decode_sumtype.v
Normal file
117
vlib/x/json2/decoder2/decode_sumtype.v
Normal file
|
@ -0,0 +1,117 @@
|
|||
module decoder2
|
||||
|
||||
import time
|
||||
|
||||
fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T) !T {
|
||||
$if initialized_sumtype is $sumtype {
|
||||
$for v in initialized_sumtype.variants {
|
||||
if initialized_sumtype is v {
|
||||
mut val := initialized_sumtype
|
||||
decoder.decode_value(mut val)!
|
||||
return T(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
return initialized_sumtype
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info ValueInfo) ! {
|
||||
$for v in val.variants {
|
||||
if value_info.value_kind == .string_ {
|
||||
$if v.typ is string {
|
||||
val = T(v)
|
||||
return
|
||||
} $else $if v.typ is time.Time {
|
||||
val = T(v)
|
||||
return
|
||||
}
|
||||
} else if value_info.value_kind == .number {
|
||||
$if v.typ is $float {
|
||||
val = T(v)
|
||||
return
|
||||
} $else $if v.typ is $int {
|
||||
val = T(v)
|
||||
return
|
||||
} $else $if v.typ is $enum {
|
||||
val = T(v)
|
||||
return
|
||||
}
|
||||
} else if value_info.value_kind == .boolean {
|
||||
$if v.typ is bool {
|
||||
val = T(v)
|
||||
return
|
||||
}
|
||||
} else if value_info.value_kind == .object {
|
||||
$if v.typ is $map {
|
||||
val = T(v)
|
||||
return
|
||||
} $else $if v.typ is $struct {
|
||||
// find "_type" field in json object
|
||||
mut type_field_node := decoder.current_node.next
|
||||
map_position := value_info.position
|
||||
map_end := map_position + value_info.length
|
||||
|
||||
type_field := '"_type"'
|
||||
|
||||
for {
|
||||
if type_field_node == unsafe { nil } {
|
||||
break
|
||||
}
|
||||
|
||||
key_info := type_field_node.value
|
||||
|
||||
if key_info.position >= map_end {
|
||||
type_field_node = unsafe { nil }
|
||||
break
|
||||
}
|
||||
|
||||
if unsafe {
|
||||
vmemcmp(decoder.json.str + key_info.position, type_field.str,
|
||||
type_field.len) == 0
|
||||
} {
|
||||
// find type field
|
||||
type_field_node = type_field_node.next
|
||||
|
||||
break
|
||||
} else {
|
||||
type_field_node = type_field_node.next
|
||||
}
|
||||
}
|
||||
|
||||
if type_field_node != unsafe { nil } {
|
||||
$for v in val.variants {
|
||||
variant_name := typeof(v.typ).name
|
||||
if type_field_node.value.length - 2 == variant_name.len {
|
||||
unsafe {
|
||||
}
|
||||
if unsafe {
|
||||
vmemcmp(decoder.json.str + type_field_node.value.position + 1,
|
||||
variant_name.str, variant_name.len) == 0
|
||||
} {
|
||||
val = T(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
} else if value_info.value_kind == .array {
|
||||
$if v.typ is $array {
|
||||
val = T(v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) decode_sumtype[T](mut val T) ! {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
decoder.init_sumtype_by_value_kind(mut val, value_info)!
|
||||
|
||||
decoded_sumtype := decoder.get_decoded_sumtype_workaround(val)!
|
||||
unsafe {
|
||||
*val = decoded_sumtype
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ pub struct Stru2 {
|
|||
churrasco string
|
||||
}
|
||||
|
||||
type SumTypes = StructType[string] | bool | int | string | time.Time
|
||||
type SumTypes = Stru | bool | int | string | time.Time
|
||||
type StringAlias = string
|
||||
type IntAlias = int
|
||||
|
||||
|
@ -48,7 +48,7 @@ mut:
|
|||
}
|
||||
|
||||
fn main() {
|
||||
json_data := '{"val": 1, "val2": "lala", "val3": {"a": 2, "churrasco": "leleu"}}'
|
||||
json_data := '{"_type": "Stru", "val": 1, "val2": "lala", "val3": {"a": 2, "churrasco": "leleu"}}'
|
||||
json_data1 := '{"val": "2"}'
|
||||
json_data2 := '{"val": 2}'
|
||||
|
||||
|
@ -72,6 +72,18 @@ fn main() {
|
|||
|
||||
b.measure('old_json.decode(Stru, json_data)!\n')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := decoder2.decode[SumTypes](json_data)!
|
||||
}
|
||||
|
||||
b.measure('decoder2.decode[SumTypes](json_data)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode(SumTypes, json_data)!
|
||||
}
|
||||
|
||||
b.measure('old_json.decode(SumTypes, json_data)!\n')
|
||||
|
||||
// StructType[string] **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := decoder2.decode[StructType[string]](json_data1)!
|
||||
|
@ -185,4 +197,18 @@ fn main() {
|
|||
}
|
||||
|
||||
b.measure('decoder2.decode[StringAlias](\'"abcdefghijklimnopqrstuv"\')!')
|
||||
|
||||
println('\n***Sumtypes***')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := decoder2.decode[SumTypes]('2')!
|
||||
}
|
||||
|
||||
b.measure('decoder2.decode[SumTypes](2)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := decoder2.decode[SumTypes]('"abcdefghijklimnopqrstuv"')!
|
||||
}
|
||||
|
||||
b.measure('decoder2.decode[SumTypes](\'"abcdefghijklimnopqrstuv"\')!')
|
||||
}
|
||||
|
|
|
@ -68,6 +68,8 @@ fn test_number() {
|
|||
// Test f32
|
||||
assert json.decode[f32]('0')! == 0.0
|
||||
assert json.decode[f32]('1')! == 1.0
|
||||
assert json.decode[f32]('1.2')! == 1.2
|
||||
assert json.decode[f32]('-1.2')! == -1.2
|
||||
assert json.decode[f32]('201')! == 201.0
|
||||
|
||||
assert json.decode[f32]('-1')! == -1.0
|
||||
|
@ -79,9 +81,11 @@ fn test_number() {
|
|||
// Test f64
|
||||
assert json.decode[f64]('0')! == 0.0
|
||||
assert json.decode[f64]('1')! == 1.0
|
||||
assert json.decode[f64]('1.2')! == 1.2
|
||||
assert json.decode[f64]('201')! == 201.0
|
||||
|
||||
assert json.decode[f64]('-1')! == -1.0
|
||||
assert json.decode[f64]('-1.2')! == -1.2
|
||||
assert json.decode[f64]('-201')! == -201.0
|
||||
|
||||
assert json.decode[f64]('1234567890')! == 1234567890.0
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import x.json2.decoder2 as json
|
||||
import x.json2
|
||||
import time
|
||||
|
||||
type Prices = Price | []Price
|
||||
|
||||
|
@ -14,17 +16,17 @@ struct Price {
|
|||
net f64
|
||||
}
|
||||
|
||||
type Animal = Cat | Dog
|
||||
pub type Animal = Cat | Dog
|
||||
|
||||
struct Cat {
|
||||
pub struct Cat {
|
||||
cat_name string
|
||||
}
|
||||
|
||||
struct Dog {
|
||||
pub struct Dog {
|
||||
dog_name string
|
||||
}
|
||||
|
||||
type Sum = int | string | bool
|
||||
type Sum = int | string | bool | []string
|
||||
|
||||
fn test_simple_sum_type() {
|
||||
assert json.decode[Sum]('1')! == Sum(1)
|
||||
|
@ -33,4 +35,48 @@ fn test_simple_sum_type() {
|
|||
|
||||
assert json.decode[Sum]('true')! == Sum(true)
|
||||
assert json.decode[Sum]('false')! == Sum(false)
|
||||
|
||||
assert json.decode[Sum]('["1", "2", "3"]')! == Sum(['1', '2', '3'])
|
||||
}
|
||||
|
||||
fn test_any_sum_type() {
|
||||
assert json.decode[json2.Any]('1')! == json2.Any(f64(1))
|
||||
assert json.decode[json2.Any]('123321')! == json2.Any(f64(123321))
|
||||
|
||||
assert json.decode[json2.Any]('"hello"')! == json2.Any('hello')
|
||||
|
||||
assert json.decode[json2.Any]('true')! == json2.Any(true)
|
||||
assert json.decode[json2.Any]('false')! == json2.Any(false)
|
||||
|
||||
assert json.decode[json2.Any]('1.1')! == json2.Any(f64(1.1))
|
||||
|
||||
// Uncomment this when #22693 is fixed
|
||||
// assert json.decode[[]json2.Any]('["1", "2", "3"]')! == [json2.Any('1'), json2.Any('2'), json2.Any('3')]
|
||||
// assert json.decode[json2.Any]('["1", "2", "3"]')! == json2.Any([json2.Any('1'), json2.Any('2'),
|
||||
// json2.Any('3')])
|
||||
|
||||
// assert json.decode[[]json2.Any]('[true, false, true]')! == [json2.Any(true), json2.Any(false),
|
||||
// json2.Any(true)]
|
||||
// assert json.decode[json2.Any]('[true, false, true]')! == json2.Any([json2.Any(true), json2.Any(false),
|
||||
// json2.Any(true)])
|
||||
|
||||
assert json.decode[json2.Any]('{"hello": "world"}')! == json2.Any({
|
||||
'hello': json2.Any('world')
|
||||
})
|
||||
|
||||
assert json.decode[map[string]json2.Any]('{"hello": "world"}')! == {
|
||||
'hello': json2.Any('world')
|
||||
}
|
||||
|
||||
// assert json.decode[json2.Any]('{"hello1": {"hello2": "world"}}')! == json2.Any({
|
||||
// 'hello1': json2.Any({
|
||||
// 'hello2': json2.Any('world')
|
||||
// })
|
||||
// })
|
||||
}
|
||||
|
||||
fn test_sum_type_struct() {
|
||||
assert json.decode[Animal]('{"cat_name": "Tom"}')! == Animal(Cat{'Tom'})
|
||||
assert json.decode[Animal]('{"dog_name": "Rex"}')! == Animal(Cat{''})
|
||||
assert json.decode[Animal]('{"dog_name": "Rex", "_type": "Dog"}')! == Animal(Dog{'Rex'})
|
||||
}
|
||||
|
|
|
@ -3,23 +3,25 @@ module json2
|
|||
import time
|
||||
|
||||
// `Any` is a sum type that lists the possible types to be decoded and used.
|
||||
pub type Any = Null
|
||||
| []Any
|
||||
// `Any` priority order for numbers: floats -> signed integers -> unsigned integers
|
||||
// `Any` priority order for strings: string -> time.Time
|
||||
pub type Any = []Any
|
||||
| bool
|
||||
| f32
|
||||
| f64
|
||||
| i16
|
||||
| i32
|
||||
| f32
|
||||
| i64
|
||||
| i8
|
||||
| int
|
||||
| i32
|
||||
| i16
|
||||
| i8
|
||||
| map[string]Any
|
||||
| string
|
||||
| time.Time
|
||||
| u16
|
||||
| u32
|
||||
| u64
|
||||
| u32
|
||||
| u16
|
||||
| u8
|
||||
| Null
|
||||
|
||||
// Decodable is an interface, that allows custom implementations for decoding structs from JSON encoded values
|
||||
pub interface Decodable {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue