mirror of
https://github.com/vlang/v.git
synced 2025-08-03 17:57:59 -04:00
decoder2: rework decoding of sumtypes (#24949)
This commit is contained in:
parent
261a4fb31b
commit
66c669fffd
@ -12,7 +12,7 @@ const false_in_string = 'false'
|
||||
|
||||
const float_zero_in_string = '0.0'
|
||||
|
||||
const whitespace_chars = [` `, `\t`, `\n`]!
|
||||
const whitespace_chars = [` `, `\t`, `\n`, `\r`]!
|
||||
|
||||
// Node represents a node in a linked list to store ValueInfo.
|
||||
struct Node[T] {
|
||||
@ -315,9 +315,6 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
// check if the JSON string is an empty array
|
||||
if checker_end >= checker.checker_idx + 2 {
|
||||
checker.checker_idx++
|
||||
if val[checker.checker_idx] == `]` {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return checker.error('EOF error: There are not enough length for an array')
|
||||
}
|
||||
@ -332,7 +329,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
|
||||
if val[checker.checker_idx] == `]` {
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
if checker.checker_idx >= checker_end - 1 {
|
||||
@ -806,8 +803,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
$if field.typ is $option {
|
||||
// it would be nicer to do this at the start of the function
|
||||
// but options cant be passed to generic functions
|
||||
if decoder.current_node.value.length == 4
|
||||
&& decoder.json[decoder.current_node.value.position..decoder.current_node.value.position + 4] == 'null' {
|
||||
if decoder.current_node.value.value_kind == .null {
|
||||
val.$(field.name) = none
|
||||
} else {
|
||||
mut unwrapped_val := create_value_from_optional(val.$(field.name)) or {
|
||||
@ -981,18 +977,6 @@ fn create_value_from_optional[T](val ?T) ?T {
|
||||
return T{}
|
||||
}
|
||||
|
||||
fn utf8_byte_len(unicode_value u32) int {
|
||||
if unicode_value <= 0x7F {
|
||||
return 1
|
||||
} else if unicode_value <= 0x7FF {
|
||||
return 2
|
||||
} else if unicode_value <= 0xFFFF {
|
||||
return 3
|
||||
} else {
|
||||
return 4
|
||||
}
|
||||
}
|
||||
|
||||
// string_buffer_to_generic_number converts a buffer of bytes (data) into a generic type T and
|
||||
// stores the result in the provided result pointer.
|
||||
// The function supports conversion to the following types:
|
||||
|
@ -2,125 +2,305 @@ module decoder2
|
||||
|
||||
import time
|
||||
|
||||
fn copy_type[T](t T) T {
|
||||
return T{}
|
||||
}
|
||||
|
||||
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 {
|
||||
// workaround for auto generated function considering sumtype as array
|
||||
unsafe {
|
||||
$if initialized_sumtype is $map {
|
||||
mut val := initialized_sumtype.clone()
|
||||
decoder.decode_value(mut val)!
|
||||
return T(val)
|
||||
} $else {
|
||||
mut val := initialized_sumtype
|
||||
decoder.decode_value(mut val)!
|
||||
return T(val)
|
||||
$if initialized_sumtype !is $option {
|
||||
mut val := copy_type(initialized_sumtype)
|
||||
decoder.decode_value(mut val)!
|
||||
return T(val)
|
||||
} $else {
|
||||
if decoder.current_node.value.value_kind == .null {
|
||||
decoder.current_node = decoder.current_node.next
|
||||
return T(initialized_sumtype)
|
||||
} else {
|
||||
decoder.error('sumtype option only support decoding null->none (for now)')!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return initialized_sumtype
|
||||
decoder.error('could not decode resolved sumtype (should not happen)')!
|
||||
return initialized_sumtype // suppress compiler error
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_element_type_valid[T](element T, current_node &Node[ValueInfo]) bool {
|
||||
if current_node == unsafe { nil } {
|
||||
$if element is $array || element is $map {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
$if element is $sumtype { // this will always match the first sumtype array/map
|
||||
return true
|
||||
}
|
||||
|
||||
match current_node.value.value_kind {
|
||||
.string_ {
|
||||
$if element is string {
|
||||
return true
|
||||
} $else $if element is time.Time {
|
||||
return true
|
||||
}
|
||||
}
|
||||
.number {
|
||||
$if element is $float {
|
||||
return true
|
||||
} $else $if element is $int {
|
||||
return true
|
||||
} $else $if element is $enum {
|
||||
return true
|
||||
}
|
||||
}
|
||||
.boolean {
|
||||
$if element is bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
.null {
|
||||
$if element is $option {
|
||||
return true
|
||||
}
|
||||
}
|
||||
.array {
|
||||
$if element is $array {
|
||||
return decoder.check_array_type_valid(element, current_node.next)
|
||||
}
|
||||
}
|
||||
.object {
|
||||
$if element is $map {
|
||||
if current_node.next != unsafe { nil } {
|
||||
return decoder.check_map_type_valid(element, current_node.next.next)
|
||||
} else {
|
||||
return decoder.check_map_type_valid(element, unsafe { nil })
|
||||
}
|
||||
} $else $if element is $struct {
|
||||
return decoder.check_struct_type_valid(element, current_node)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fn get_array_element_type[T](arr []T) T {
|
||||
return T{}
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_array_type_valid[T](arr []T, current_node &Node[ValueInfo]) bool {
|
||||
return decoder.check_element_type_valid(get_array_element_type(arr), current_node)
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_array_type_workaround[T](initialized_sumtype T) bool {
|
||||
$if initialized_sumtype is $sumtype {
|
||||
$for v in initialized_sumtype.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is $array {
|
||||
return decoder.check_element_type_valid(initialized_sumtype, decoder.current_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn get_map_element_type[U, V](m map[U]V) V {
|
||||
return V{}
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_map_type_valid[T](m T, current_node &Node[ValueInfo]) bool {
|
||||
element := get_map_element_type(m)
|
||||
return decoder.check_element_type_valid(element, current_node)
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_map_empty_valid[T](m T) bool {
|
||||
element := get_map_element_type(m)
|
||||
return decoder.check_element_type_valid(element, current_node)
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_map_type_workaround[T](initialized_sumtype T) bool {
|
||||
$if initialized_sumtype is $sumtype {
|
||||
$for v in initialized_sumtype.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is $map {
|
||||
val := copy_type(initialized_sumtype)
|
||||
if decoder.current_node.next != unsafe { nil } {
|
||||
return decoder.check_map_type_valid(val, decoder.current_node.next.next)
|
||||
} else {
|
||||
return decoder.check_map_type_valid(val, unsafe { nil })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_struct_type_valid[T](s T, current_node Node[ValueInfo]) bool {
|
||||
// find "_type" field in json object
|
||||
mut type_field_node := decoder.current_node.next
|
||||
map_position := current_node.value.position
|
||||
map_end := map_position + current_node.value.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 } {
|
||||
return false
|
||||
}
|
||||
|
||||
variant_name := typeof(s).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
|
||||
} {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_struct_type_workaround[T](initialized_sumtype T) bool {
|
||||
$if initialized_sumtype is $sumtype {
|
||||
$for v in initialized_sumtype.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is $struct {
|
||||
val := copy_type(initialized_sumtype)
|
||||
return decoder.check_struct_type_valid(val, decoder.current_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
mut failed_struct := false
|
||||
|
||||
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
|
||||
}
|
||||
match value_info.value_kind {
|
||||
.string_ {
|
||||
$for v in val.variants {
|
||||
$if v.typ is string {
|
||||
val = T(v)
|
||||
return
|
||||
} $else $if v.typ is time.Time {
|
||||
val = T(v)
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
.number {
|
||||
$for v in val.variants {
|
||||
$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
|
||||
}
|
||||
}
|
||||
}
|
||||
.boolean {
|
||||
$for v in val.variants {
|
||||
$if v.typ is bool {
|
||||
val = T(v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
.null {
|
||||
$for v in val.variants {
|
||||
$if v.typ is $option {
|
||||
val = T(v)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
.array {
|
||||
$for v in val.variants {
|
||||
$if v.typ is $array {
|
||||
val = T(v)
|
||||
|
||||
if decoder.get_array_type_workaround(val) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.object {
|
||||
$for v in val.variants {
|
||||
$if v.typ is $map {
|
||||
val = T(v)
|
||||
|
||||
if decoder.get_map_type_workaround(val) {
|
||||
return
|
||||
}
|
||||
} $else $if v.typ is $struct {
|
||||
val = T(v)
|
||||
|
||||
if decoder.get_struct_type_workaround(val) {
|
||||
return
|
||||
}
|
||||
|
||||
failed_struct = true
|
||||
}
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
if failed_struct {
|
||||
decoder.error('could not resolve sumtype `${T.name}`, missing "_type" field?')!
|
||||
}
|
||||
|
||||
decoder.error('could not resolve sumtype `${T.name}`, got ${value_info.value_kind}.')!
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) decode_sumtype[T](mut val T) ! {
|
||||
value_info := decoder.current_node.value
|
||||
$if T is $alias {
|
||||
decoder.error('Type aliased sumtypes not supported.')!
|
||||
} $else {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
decoder.init_sumtype_by_value_kind(mut val, value_info)!
|
||||
decoder.init_sumtype_by_value_kind(mut val, value_info)!
|
||||
|
||||
decoded_sumtype := decoder.get_decoded_sumtype_workaround(val)!
|
||||
unsafe {
|
||||
*val = decoded_sumtype
|
||||
val = decoder.get_decoded_sumtype_workaround(val)!
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,21 @@ type Sum = int | string | bool | []string
|
||||
|
||||
type StructSumTypes = Stru | Stru2
|
||||
|
||||
type Mixed = Cat | int | map[string]int
|
||||
|
||||
type Maybes = ?int | ?string
|
||||
|
||||
type MultiArray = []int
|
||||
| []bool
|
||||
| [][]string
|
||||
| []map[string]map[string][]int
|
||||
| map[string]json2.Any
|
||||
| string
|
||||
|
||||
type StructLists = Cat | []Cat | map[string]Dog
|
||||
|
||||
type SumAlias = Sum
|
||||
|
||||
pub struct Stru {
|
||||
val int
|
||||
val2 string
|
||||
@ -42,6 +57,8 @@ pub struct Stru2 {
|
||||
steak string
|
||||
}
|
||||
|
||||
type NewAny = int | string | bool | []NewAny | map[string]NewAny | ?int
|
||||
|
||||
fn test_simple_sum_type() {
|
||||
assert json.decode[Sum]('1')! == Sum(1)
|
||||
|
||||
@ -86,13 +103,62 @@ fn test_any_sum_type() {
|
||||
'hello2': json2.Any('world')
|
||||
})
|
||||
})
|
||||
|
||||
assert json.decode[NewAny]('{"name": null, "value": "hi"}')! == NewAny({
|
||||
'name': NewAny(?int(none))
|
||||
'value': NewAny('hi')
|
||||
})
|
||||
|
||||
assert json.decode[json2.Any]('[]')! == json2.Any([]json2.Any{})
|
||||
assert json.decode[json2.Any]('{}')! == json2.Any(map[string]json2.Any{})
|
||||
}
|
||||
|
||||
fn test_sum_type_struct() {
|
||||
assert json.decode[Animal]('{"cat_name": "Tom"}')! == Animal(Cat{'Tom'})
|
||||
assert json.decode[Animal]('{"dog_name": "Rex"}')! == Animal(Cat{''})
|
||||
if x := json.decode[Animal]('{"cat_name": "Tom"}') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[Animal]('{"dog_name": "Rex"}') {
|
||||
assert false
|
||||
}
|
||||
assert json.decode[Animal]('{"dog_name": "Rex", "_type": "Dog"}')! == Animal(Dog{'Rex'})
|
||||
|
||||
// struct sumtype in random order
|
||||
assert json.decode[StructSumTypes]('{"_type": "Stru", "val": 1, "val2": "lala", "val3": {"a": 2, "steak": "leleu"}, "val4": 2147483000, "val5": 2147483000}')! == StructSumTypes(Stru{1, 'lala', Stru2{2, 'leleu'}, 2147483000, 2147483000})
|
||||
}
|
||||
|
||||
fn test_sum_type_mixed() {
|
||||
assert json.decode[Mixed]('{"key":0}')! == Mixed({
|
||||
'key': 0
|
||||
})
|
||||
assert json.decode[Mixed]('10')! == Mixed(10)
|
||||
}
|
||||
|
||||
// to be implemented
|
||||
fn test_sum_type_options_fail() {
|
||||
assert json.decode[Maybes]('null')! == Maybes(?int(none))
|
||||
if x := json.decode[Maybes]('99') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[Maybes]('hi') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[Maybes]('true') {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
// to be implemented
|
||||
fn test_sum_type_alias_fail() {
|
||||
if x := json.decode[SumAlias]('99') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[SumAlias]('true') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[SumAlias]('["hi", "bye"]') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[SumAlias]('[0, 1]') {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user