x.json2.decoder: support sumtypes fully (#22694)

This commit is contained in:
Hitalo Souza 2024-10-30 03:01:39 -04:00 committed by GitHub
parent 6e9a66dbf3
commit 54a6915f02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 214 additions and 63 deletions

View file

@ -549,19 +549,6 @@ pub fn decode[T](val string) !T {
return result 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. // decode_value decodes a value from the JSON nodes.
fn (mut decoder Decoder) decode_value[T](mut val T) ! { fn (mut decoder Decoder) decode_value[T](mut val T) ! {
$if T is $option { $if T is $option {
@ -620,38 +607,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
val = string_buffer.bytestr() val = string_buffer.bytestr()
} }
} $else $if T.unaliased_typ is $sumtype { } $else $if T.unaliased_typ is $sumtype {
value_info := decoder.current_node.value decoder.decode_sumtype(mut val)!
$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
}
} $else $if T.unaliased_typ is time.Time { } $else $if T.unaliased_typ is time.Time {
time_info := decoder.current_node.value time_info := decoder.current_node.value
@ -768,7 +724,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
unsafe { unsafe {
val = vmemcmp(decoder.json.str + value_info.position, 'true'.str, 4) == 0 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 value_info := decoder.current_node.value
if value_info.value_kind == .number { 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 { } $else $if T.unaliased_typ is $float {
mut is_negative := false mut is_negative := false
mut decimal_seen := false mut decimal_seen := false
mut decimal_divider := int(1) mut decimal_divider := T(1)
for ch in data { for ch in data {
if ch == `-` { if ch == `-` {
@ -965,13 +921,13 @@ pub fn string_buffer_to_generic_number[T](result &T, data []u8) {
continue continue
} }
digit := T(ch - `0`) digit := T(ch - u8(`0`))
if decimal_seen { if decimal_seen {
decimal_divider *= 10 decimal_divider *= 10
*result += T(digit / decimal_divider) *result += T(digit / decimal_divider)
} else { } else {
*result = *result * 10 + digit *result = T(*result * 10 + digit)
} }
} }
if is_negative { if is_negative {

View 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
}
}

View file

@ -19,7 +19,7 @@ pub struct Stru2 {
churrasco string churrasco string
} }
type SumTypes = StructType[string] | bool | int | string | time.Time type SumTypes = Stru | bool | int | string | time.Time
type StringAlias = string type StringAlias = string
type IntAlias = int type IntAlias = int
@ -48,7 +48,7 @@ mut:
} }
fn main() { 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_data1 := '{"val": "2"}'
json_data2 := '{"val": 2}' json_data2 := '{"val": 2}'
@ -72,6 +72,18 @@ fn main() {
b.measure('old_json.decode(Stru, json_data)!\n') 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] ********************************************************** // StructType[string] **********************************************************
for i := 0; i < max_iterations; i++ { for i := 0; i < max_iterations; i++ {
_ := decoder2.decode[StructType[string]](json_data1)! _ := decoder2.decode[StructType[string]](json_data1)!
@ -185,4 +197,18 @@ fn main() {
} }
b.measure('decoder2.decode[StringAlias](\'"abcdefghijklimnopqrstuv"\')!') 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"\')!')
} }

View file

@ -68,6 +68,8 @@ fn test_number() {
// Test f32 // Test f32
assert json.decode[f32]('0')! == 0.0 assert json.decode[f32]('0')! == 0.0
assert json.decode[f32]('1')! == 1.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]('201')! == 201.0
assert json.decode[f32]('-1')! == -1.0 assert json.decode[f32]('-1')! == -1.0
@ -79,9 +81,11 @@ fn test_number() {
// Test f64 // Test f64
assert json.decode[f64]('0')! == 0.0 assert json.decode[f64]('0')! == 0.0
assert json.decode[f64]('1')! == 1.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]('201')! == 201.0
assert json.decode[f64]('-1')! == -1.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]('-201')! == -201.0
assert json.decode[f64]('1234567890')! == 1234567890.0 assert json.decode[f64]('1234567890')! == 1234567890.0

View file

@ -1,4 +1,6 @@
import x.json2.decoder2 as json import x.json2.decoder2 as json
import x.json2
import time
type Prices = Price | []Price type Prices = Price | []Price
@ -14,17 +16,17 @@ struct Price {
net f64 net f64
} }
type Animal = Cat | Dog pub type Animal = Cat | Dog
struct Cat { pub struct Cat {
cat_name string cat_name string
} }
struct Dog { pub struct Dog {
dog_name string dog_name string
} }
type Sum = int | string | bool type Sum = int | string | bool | []string
fn test_simple_sum_type() { fn test_simple_sum_type() {
assert json.decode[Sum]('1')! == Sum(1) 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]('true')! == Sum(true)
assert json.decode[Sum]('false')! == Sum(false) 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'})
} }

View file

@ -3,23 +3,25 @@ module json2
import time import time
// `Any` is a sum type that lists the possible types to be decoded and used. // `Any` is a sum type that lists the possible types to be decoded and used.
pub type Any = Null // `Any` priority order for numbers: floats -> signed integers -> unsigned integers
| []Any // `Any` priority order for strings: string -> time.Time
pub type Any = []Any
| bool | bool
| f32
| f64 | f64
| i16 | f32
| i32
| i64 | i64
| i8
| int | int
| i32
| i16
| i8
| map[string]Any | map[string]Any
| string | string
| time.Time | time.Time
| u16
| u32
| u64 | u64
| u32
| u16
| u8 | u8
| Null
// Decodable is an interface, that allows custom implementations for decoding structs from JSON encoded values // Decodable is an interface, that allows custom implementations for decoding structs from JSON encoded values
pub interface Decodable { pub interface Decodable {