vlib/json2/decode_sumtype.v
Rama master · réplica de lectura del repositorio original.
module json2
import time
struct SumtypeTimeValue {
typ string @[json: '_type'; required]
value i64 @[required]
}
fn sumtype_variant_name(type_name string) string {
return type_name.all_after_last('.')
}
// struct_variant_tag returns the `_type` of a struct sum type variant of type `T`: the
// struct's own name, also for an alias of it (`Foo` for `type Alias = Foo`), like the
// removed `json` module wrote it.
fn struct_variant_tag[T]() string {
return sumtype_variant_name(typeof($zero(T.unaliased_typ)).name)
}
// option_payload_tag returns the `_type` of an option variant of a struct or a time.
fn option_payload_tag[P](_ ?P) string {
return struct_variant_tag[P]()
}
fn (mut decoder Decoder) decode_selected_sumtype[T](mut value T) ! {
type_idx := value.type_idx()
$for variant in T.variants {
// Keep the narrowing in its own condition for generic variant inference.
if mut value is variant {
if type_idx == variant.typ {
$if variant.typ !is $option && variant.typ is time.Time {
decoder.decode_sumtype_time(mut value)!
} $else {
decoder.decode_value(mut value)!
}
return
}
}
}
decoder.decode_error('could not decode resolved sumtype (should not happen)')!
}
// check_element_type_valid reports whether the value at `value_idx` in values_info
// has the shape of `element`.
fn (mut decoder Decoder) check_element_type_valid[T](element T, value_idx int) bool {
if !decoder.has_value(value_idx) {
$if element is $array || element is $map {
return false
}
return true
}
value_kind := decoder.values_info[value_idx].value_kind
$if element is $sumtype { // this will always match the first sumtype array/map
return true
}
$if element is $option {
// A `none` element is `null`, or `{}` as the removed module wrote it; a set one
// has the shape of its payload.
if value_kind == .null || decoder.is_empty_object(value_idx) {
return true
}
return decoder.check_option_element_valid(element, value_idx)
}
match value_kind {
.string {
$if element is string {
return true
} $else $if element is time.Time {
return true
} $else $if element is StringDecoder {
return true
}
}
.number {
$if element is $float {
return true
} $else $if element is $int {
return true
} $else $if element is $enum {
return true
} $else $if element is NumberDecoder {
return true
}
}
.boolean {
$if element is bool {
return true
} $else $if element is BooleanDecoder {
return true
}
}
.null {
$if element is $option {
return true
} $else $if element is NullDecoder {
return true
}
}
.array {
$if element is $array {
return decoder.check_array_type_valid(element, value_idx + 1)
}
}
.object {
$if element is $map {
// The first value of the object, after its first key.
return decoder.check_map_type_valid(element, value_idx + 2)
} $else $if element is $struct {
return decoder.check_struct_type_valid(element, value_idx)
}
}
}
return false
}
fn (mut decoder Decoder) check_option_element_valid[P](_ ?P, value_idx int) bool {
return decoder.check_element_type_valid(P{}, value_idx)
}
// is_empty_object reports whether the value at `value_idx` is an object without
// members (`{}`).
fn (decoder &Decoder) is_empty_object(value_idx int) bool {
value_info := decoder.values_info[value_idx]
if value_info.value_kind != .object {
return false
}
end := value_info.position + value_info.length
return !decoder.has_value(value_idx + 1) || decoder.values_info[value_idx + 1].position >= end
}
fn get_array_element_type[T](_arr []T) T {
return T{}
}
fn (mut decoder Decoder) check_array_type_valid[T](arr []T, value_idx int) bool {
element := get_array_element_type(arr)
return decoder.check_element_type_valid(element, value_idx)
}
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, value_idx int) bool {
element := get_map_element_type(m)
return decoder.check_element_type_valid(element, value_idx)
}
// get_sumtype_type_field_idx returns the index in values_info of the value of the
// `_type` key of the object at `value_idx`, or no_value_idx without such a key.
@[direct_array_access; markused]
fn (mut decoder Decoder) get_sumtype_type_field_idx(value_idx int) int {
if !decoder.has_value(value_idx) || decoder.values_info[value_idx].value_kind != .object {
return no_value_idx
}
// Look at the object's own keys only: a nested value (such as a field holding
// another sum type, which is encoded before the outer `_type`) can have a
// `_type` of its own.
object_info := decoder.values_info[value_idx]
object_end := object_info.position + object_info.length
values_len := decoder.values_info.len
mut key_idx := value_idx + 1
for key_idx < values_len && decoder.values_info[key_idx].position < object_end {
key_value_idx := key_idx + 1
if key_value_idx >= values_len {
break
}
// A key spelled with escapes (`"_\u0074ype"`) is `_type` after unescaping.
if decoder.json_key_matches(decoder.values_info[key_idx], '_type') or { false } {
return key_value_idx
}
// Skip the value, with everything nested in it.
key_idx = decoder.next_value(key_value_idx)
}
return no_value_idx
}
@[markused]
fn (mut decoder Decoder) sumtype_type_field_matches(type_field_idx int, expected string) bool {
if !decoder.has_value(type_field_idx) {
return false
}
value_info := decoder.values_info[type_field_idx]
if value_info.value_kind != .string {
return false
}
body := decoder.json[value_info.position + 1..value_info.position + value_info.length - 1]
if body.index_u8(`\\`) != -1 {
// An escaped discriminator (`"Hum\u0061n"`) names the variant after unescaping.
decoded := decoder.decode_string_value(value_info) or { return false }
return decoded == expected
}
return body == expected
}
fn (mut decoder Decoder) check_sumtype_type_valid[T](value T, value_idx int) bool {
type_field_idx := decoder.get_sumtype_type_field_idx(value_idx)
// An alias of a struct is tagged with its own name, or with the struct's name.
return decoder.sumtype_type_field_matches(type_field_idx,
sumtype_variant_name(typeof(value).name))
|| decoder.sumtype_type_field_matches(type_field_idx, struct_variant_tag[T]())
}
fn (mut decoder Decoder) check_struct_type_valid[T](s T, value_idx int) bool {
return decoder.check_sumtype_type_valid(s, value_idx)
}
// resolve_sumtype_from_type_field selects the struct (or time.Time) variant named by
// the object's `_type` field. The name is matched before anything is constructed:
// building a variant runs its field defaults, which may have side effects, so only
// the selected variant may be built.
fn (mut decoder Decoder) resolve_sumtype_from_type_field[T](mut val T) !bool {
type_field_idx := decoder.get_sumtype_type_field_idx(decoder.current_idx)
if type_field_idx == no_value_idx {
return false
}
mut has_discriminated_variant := false
// A variant is tagged with its payload's name, like in the removed `json` module:
// the struct's name for an alias of a struct (`Foo` for `type Alias = Foo`), and
// `Time` for a time or an alias of it. The variant's own name is accepted too, and
// is tried first, so `Foo | Alias` tells both apart.
for base_names in [false, true] {
$for v in T.variants {
$if v.typ is $option {
// An option of a struct or a time (`?Foo`, `?time.Time`) is tagged with
// its payload's name.
option_value := $zero(v.typ)
if option_payload_fit(option_value, .object) > 0 {
has_discriminated_variant = true
name := if base_names {
option_payload_tag(option_value)
} else {
sumtype_variant_name(typeof(v.typ).name.trim_left('?'))
}
if decoder.sumtype_type_field_matches(type_field_idx, name) {
val = T(v)
return true
}
}
} $else $if v.typ is $struct {
has_discriminated_variant = true
name := if base_names {
sumtype_variant_name(typeof(v.typ.unaliased_typ).name)
} else {
sumtype_variant_name(typeof(v.typ).name)
}
if decoder.sumtype_type_field_matches(type_field_idx, name) {
val = T(v)
return true
}
}
}
}
if !has_discriminated_variant {
return false
}
decoder.decode_error('could not resolve sumtype `${T.name}` from "_type" field')!
return false
}
@[markused]
fn (mut decoder Decoder) decode_sumtype_time(mut val time.Time) ! {
mut wrapper := SumtypeTimeValue{
typ: ''
value: 0
}
decoder.decode_value(mut wrapper)!
val = time.unix(wrapper.value)
}
fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info ValueInfo) ! {
mut failed_struct := false
mut struct_variant_count := 0
// For a string, number or boolean, a variant of the plain type wins over an alias
// of it (`MyString | string` selects `string`), like in the removed `json` module:
// aliases are only tried after all other variants.
match value_info.value_kind {
.string {
for aliases in [false, true] {
$for v in T.variants {
mut is_alias := false
$if v.typ is $alias {
is_alias = true
}
if is_alias == aliases {
$if v.typ is string {
val = T(v)
return
} $else $if v.typ is time.Time {
val = T(v)
return
} $else $if v.typ is StringDecoder {
val = T(v)
return
}
}
}
}
}
.number {
for aliases in [false, true] {
$for v in T.variants {
mut is_alias := false
$if v.typ is $alias {
is_alias = true
}
if is_alias == aliases {
$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 v.typ is NumberDecoder {
val = T(v)
return
}
}
}
}
}
.boolean {
for aliases in [false, true] {
$for v in T.variants {
mut is_alias := false
$if v.typ is $alias {
is_alias = true
}
if is_alias == aliases {
$if v.typ is bool {
val = T(v)
return
} $else $if v.typ is BooleanDecoder {
val = T(v)
return
}
}
}
}
}
.null {
$for v in T.variants {
$if v.typ is $option {
val = T(v)
return
} $else $if v.typ is NullDecoder {
val = T(v)
return
}
}
}
.array {
$for v in T.variants {
$if v.typ is $array {
candidate := $zero(v.typ)
if decoder.check_element_type_valid(candidate, decoder.current_idx) {
val = T(candidate)
return
}
}
}
}
.object {
if decoder.resolve_sumtype_from_type_field(mut val)! {
return
}
$for v in T.variants {
$if v.typ is $map {
candidate := $zero(v.typ)
if decoder.check_element_type_valid(candidate, decoder.current_idx) {
val = T(candidate)
return
}
} $else $if v.typ is $struct {
// Without a `_type` field no struct variant matches by name; the
// only one that can be selected is built below.
struct_variant_count++
failed_struct = true
}
}
}
}
if failed_struct {
// If there is only one struct variant and no explicit `_type` key,
// the object shape is already unambiguous.
if struct_variant_count == 1
&& decoder.get_sumtype_type_field_idx(decoder.current_idx) == no_value_idx {
$for v in T.variants {
$if v.typ is $struct {
val = T(v)
}
}
return
}
decoder.decode_error('could not resolve sumtype `${T.name}`, missing "_type" field?')!
}
// A value no other variant takes goes to an option variant, whose payload has to
// decode it (`5` for `?int | string`), like in the removed `json` module: first one
// whose payload takes the JSON value as it is (`?string` for a string), then one
// that converts it (`?rune` for a string), then any.
if value_info.value_kind != .null {
for min_fit in [2, 1, 0] {
$for v in T.variants {
$if v.typ is $option {
if option_payload_fit($zero(v.typ), value_info.value_kind) >= min_fit {
val = T(v)
return
}
}
}
}
}
decoder.decode_error('could not resolve sumtype `${T.name}`, got ${value_info.value_kind}.')!
}
// option_payload_fit reports how the payload of an option (`?int`) takes a JSON value
// of `kind`: 2 when it is of that kind (`?string` for a string), 1 when it converts it
// (`?rune` or `?time.Time` for a string), and 0 when it does not take it. Aliases are
// unwrapped, so `?Text` takes a string for `type Text = string`.
fn option_payload_fit[P](_ ?P, kind ValueKind) int {
$if P.unaliased_typ is time.Time {
return if kind == .string || kind == .object { 1 } else { 0 }
} $else $if P.unaliased_typ is string {
return if kind == .string { 2 } else { 0 }
} $else $if P.unaliased_typ is bool {
return if kind == .boolean { 2 } else { 0 }
} $else $if P.unaliased_typ is rune {
// A rune is written as a string, and a number is taken as its code point.
return if kind == .string || kind == .number { 1 } else { 0 }
} $else $if P.unaliased_typ is $int || P.unaliased_typ is $float {
return if kind == .number { 2 } else { 0 }
} $else $if P.unaliased_typ is $enum {
return if kind == .string || kind == .number { 1 } else { 0 }
} $else $if P.unaliased_typ is $array || P.unaliased_typ is $array_fixed {
return if kind == .array { 2 } else { 0 }
} $else {
return if kind == .object { 2 } else { 0 }
}
}
fn (mut decoder Decoder) decode_sumtype[T](mut val T) ! {
value_info := decoder.current_value()
decoder.init_sumtype_by_value_kind(mut val, value_info)!
decoder.decode_selected_sumtype(mut val)!
}