mirror of
https://github.com/vlang/v.git
synced 2025-08-03 09:47:15 -04:00
decoder2: support custom decoders (#25021)
This commit is contained in:
parent
b628626923
commit
809e617501
30
vlib/math/big/json_decode.v
Normal file
30
vlib/math/big/json_decode.v
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module big
|
||||||
|
|
||||||
|
// from_json_number implements a custom decoder for json2
|
||||||
|
pub fn (mut result Integer) from_json_number(raw_number string) ! {
|
||||||
|
mut index := 0
|
||||||
|
mut is_negative := false
|
||||||
|
|
||||||
|
if raw_number[0] == `-` {
|
||||||
|
is_negative = true
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
ten := integer_from_int(10)
|
||||||
|
|
||||||
|
for index < raw_number.len {
|
||||||
|
digit := raw_number[index] - `0`
|
||||||
|
|
||||||
|
if digit > 9 { // comma, e and E are all smaller 0 in ASCII so they underflow
|
||||||
|
return error('expected integer but got real number')
|
||||||
|
}
|
||||||
|
|
||||||
|
result = (result * ten) + integer_from_int(int(digit))
|
||||||
|
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_negative {
|
||||||
|
result = result * integer_from_int(-1)
|
||||||
|
}
|
||||||
|
}
|
6
vlib/time/json_decode.c.v
Normal file
6
vlib/time/json_decode.c.v
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module time
|
||||||
|
|
||||||
|
// from_json_string implements a custom decoder for json2
|
||||||
|
pub fn (mut t Time) from_json_string(raw_string string) ! {
|
||||||
|
t = parse_rfc3339(raw_string) or { Time{} }
|
||||||
|
}
|
50
vlib/x/json2/decoder2/custom.v
Normal file
50
vlib/x/json2/decoder2/custom.v
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
module decoder2
|
||||||
|
|
||||||
|
// implements decoding json strings, e.g. "hello, \u2164!"
|
||||||
|
pub interface StringDecoder {
|
||||||
|
mut:
|
||||||
|
// called with raw string (minus apostrophes) e.g. 'hello, \u2164!'
|
||||||
|
from_json_string(raw_string string) !
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements decoding json numbers, e.g. -1.234e23
|
||||||
|
pub interface NumberDecoder {
|
||||||
|
mut:
|
||||||
|
// called with raw string of number e.g. '-1.234e23'
|
||||||
|
from_json_number(raw_number string) !
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements decoding json true/false
|
||||||
|
pub interface BooleanDecoder {
|
||||||
|
mut:
|
||||||
|
// called with converted bool
|
||||||
|
// already checked so no error needed
|
||||||
|
from_json_boolean(boolean_value bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements decoding json null
|
||||||
|
pub interface NullDecoder {
|
||||||
|
mut:
|
||||||
|
// only has one value
|
||||||
|
// already checked so no error needed
|
||||||
|
from_json_null()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement once generic interfaces are more stable
|
||||||
|
|
||||||
|
// // implements decoding json arrays, e.g. ["hi", "bye", 0.9, true]
|
||||||
|
// // elements are already decoded
|
||||||
|
// pub interface ArrayDecoder[T] {
|
||||||
|
// mut:
|
||||||
|
// // called for every element in array e.g. 'hi', 'bye', '0.9', 'true'
|
||||||
|
// from_json_value(raw_value T)!
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // implements decoding json object, e.g. {"name": "foo", "name": "bar", "age": 99}
|
||||||
|
// // this allows for duplicate/ordered keys to be decoded
|
||||||
|
// // elements are already decoded
|
||||||
|
// pub interface ObjectDecoder[T] {
|
||||||
|
// mut:
|
||||||
|
// // called for every key-value pair in object (minus apostrophes for keys) e.g. ('name': 'foo'), ('name': 'bar'), ('age': '99')
|
||||||
|
// from_json_pair(raw_key string, raw_value T)!
|
||||||
|
// }
|
@ -1,7 +1,6 @@
|
|||||||
module decoder2
|
module decoder2
|
||||||
|
|
||||||
import strconv
|
import strconv
|
||||||
import time
|
|
||||||
import strings
|
import strings
|
||||||
|
|
||||||
const null_in_string = 'null'
|
const null_in_string = 'null'
|
||||||
@ -533,8 +532,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
|||||||
}
|
}
|
||||||
.number {
|
.number {
|
||||||
// check if the JSON string is a valid float or integer
|
// check if the JSON string is a valid float or integer
|
||||||
|
if val[checker.checker_idx] == `-` {
|
||||||
if val[0] == `-` {
|
|
||||||
checker.checker_idx++
|
checker.checker_idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,15 +784,6 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
|||||||
} $else $if T.unaliased_typ is $sumtype {
|
} $else $if T.unaliased_typ is $sumtype {
|
||||||
decoder.decode_sumtype(mut val)!
|
decoder.decode_sumtype(mut val)!
|
||||||
return
|
return
|
||||||
} $else $if T.unaliased_typ is time.Time {
|
|
||||||
time_info := decoder.current_node.value
|
|
||||||
|
|
||||||
if time_info.value_kind == .string_ {
|
|
||||||
string_time := decoder.json.substr_unsafe(time_info.position + 1, time_info.position +
|
|
||||||
time_info.length - 1)
|
|
||||||
|
|
||||||
val = time.parse_rfc3339(string_time) or { time.Time{} }
|
|
||||||
}
|
|
||||||
} $else $if T.unaliased_typ is $map {
|
} $else $if T.unaliased_typ is $map {
|
||||||
decoder.decode_map(mut val)!
|
decoder.decode_map(mut val)!
|
||||||
return
|
return
|
||||||
@ -811,6 +800,50 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
|||||||
} $else $if T.unaliased_typ is $struct {
|
} $else $if T.unaliased_typ is $struct {
|
||||||
struct_info := decoder.current_node.value
|
struct_info := decoder.current_node.value
|
||||||
|
|
||||||
|
// Custom Decoders
|
||||||
|
$if val is StringDecoder {
|
||||||
|
if struct_info.value_kind == .string_ {
|
||||||
|
val.from_json_string(decoder.json[struct_info.position + 1..struct_info.position +
|
||||||
|
struct_info.length - 1])!
|
||||||
|
if decoder.current_node != unsafe { nil } {
|
||||||
|
decoder.current_node = decoder.current_node.next
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$if val is NumberDecoder {
|
||||||
|
if struct_info.value_kind == .number {
|
||||||
|
val.from_json_number(decoder.json[struct_info.position..struct_info.position +
|
||||||
|
struct_info.length])!
|
||||||
|
if decoder.current_node != unsafe { nil } {
|
||||||
|
decoder.current_node = decoder.current_node.next
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$if val is BooleanDecoder {
|
||||||
|
if struct_info.value_kind == .boolean {
|
||||||
|
val.from_json_boolean(decoder.json[struct_info.position] == `t`)
|
||||||
|
if decoder.current_node != unsafe { nil } {
|
||||||
|
decoder.current_node = decoder.current_node.next
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$if val is NullDecoder {
|
||||||
|
if struct_info.value_kind == .null {
|
||||||
|
val.from_json_null()
|
||||||
|
if decoder.current_node != unsafe { nil } {
|
||||||
|
decoder.current_node = decoder.current_node.next
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// struct field info linked list
|
// struct field info linked list
|
||||||
mut struct_fields_info := LinkedList[StructFieldInfo]{}
|
mut struct_fields_info := LinkedList[StructFieldInfo]{}
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ fn (mut decoder Decoder) check_element_type_valid[T](element T, current_node &No
|
|||||||
return true
|
return true
|
||||||
} $else $if element is time.Time {
|
} $else $if element is time.Time {
|
||||||
return true
|
return true
|
||||||
|
} $else $if element is StringDecoder {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.number {
|
.number {
|
||||||
@ -56,16 +58,22 @@ fn (mut decoder Decoder) check_element_type_valid[T](element T, current_node &No
|
|||||||
return true
|
return true
|
||||||
} $else $if element is $enum {
|
} $else $if element is $enum {
|
||||||
return true
|
return true
|
||||||
|
} $else $if element is NumberDecoder {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.boolean {
|
.boolean {
|
||||||
$if element is bool {
|
$if element is bool {
|
||||||
return true
|
return true
|
||||||
|
} $else $if element is BooleanDecoder {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.null {
|
.null {
|
||||||
$if element is $option {
|
$if element is $option {
|
||||||
return true
|
return true
|
||||||
|
} $else $if element is NullDecoder {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.array {
|
.array {
|
||||||
@ -221,6 +229,9 @@ fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info Val
|
|||||||
} $else $if v.typ is time.Time {
|
} $else $if v.typ is time.Time {
|
||||||
val = T(v)
|
val = T(v)
|
||||||
return
|
return
|
||||||
|
} $else $if v.typ is StringDecoder {
|
||||||
|
val = T(v)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,6 +246,9 @@ fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info Val
|
|||||||
} $else $if v.typ is $enum {
|
} $else $if v.typ is $enum {
|
||||||
val = T(v)
|
val = T(v)
|
||||||
return
|
return
|
||||||
|
} $else $if v.typ is NumberDecoder {
|
||||||
|
val = T(v)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,6 +257,9 @@ fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info Val
|
|||||||
$if v.typ is bool {
|
$if v.typ is bool {
|
||||||
val = T(v)
|
val = T(v)
|
||||||
return
|
return
|
||||||
|
} $else $if v.typ is BooleanDecoder {
|
||||||
|
val = T(v)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -251,6 +268,9 @@ fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info Val
|
|||||||
$if v.typ is $option {
|
$if v.typ is $option {
|
||||||
val = T(v)
|
val = T(v)
|
||||||
return
|
return
|
||||||
|
} $else $if v.typ is NullDecoder {
|
||||||
|
val = T(v)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
89
vlib/x/json2/decoder2/tests/decode_custom_test.v
Normal file
89
vlib/x/json2/decoder2/tests/decode_custom_test.v
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import decoder2 as json
|
||||||
|
import x.json2
|
||||||
|
import math.big
|
||||||
|
|
||||||
|
struct MyString implements json.StringDecoder, json.NumberDecoder, json.BooleanDecoder, json.NullDecoder {
|
||||||
|
mut:
|
||||||
|
data string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ms MyString) from_json_string(raw_string string) ! {
|
||||||
|
ms.data = raw_string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ms MyString) from_json_number(raw_number string) ! {
|
||||||
|
mut first := true
|
||||||
|
|
||||||
|
for digit in raw_number {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
ms.data += '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.data += match digit {
|
||||||
|
`-` { 'minus' }
|
||||||
|
`.` { 'dot' }
|
||||||
|
`e`, `E` { 'e' }
|
||||||
|
`0` { 'zero' }
|
||||||
|
`1` { 'one' }
|
||||||
|
`2` { 'two' }
|
||||||
|
`3` { 'three' }
|
||||||
|
`4` { 'four' }
|
||||||
|
`5` { 'five' }
|
||||||
|
`6` { 'six' }
|
||||||
|
`7` { 'seven' }
|
||||||
|
`8` { 'eight' }
|
||||||
|
`9` { 'nine' }
|
||||||
|
else { 'none' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ms MyString) from_json_boolean(boolean_value bool) {
|
||||||
|
ms.data = if boolean_value { 'yes' } else { 'no' }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (mut ms MyString) from_json_null() {
|
||||||
|
ms.data = 'default value'
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NoCustom {
|
||||||
|
a int
|
||||||
|
b string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_custom() {
|
||||||
|
assert json.decode[NoCustom]('{"a": 99, "b": "hi"}')! == NoCustom{
|
||||||
|
a: 99
|
||||||
|
b: 'hi'
|
||||||
|
}
|
||||||
|
|
||||||
|
assert json.decode[[]MyString]('["hi", -9.8e7, true, null]')! == [
|
||||||
|
MyString{
|
||||||
|
data: 'hi'
|
||||||
|
},
|
||||||
|
MyString{
|
||||||
|
data: 'minus-nine-dot-eight-e-seven'
|
||||||
|
},
|
||||||
|
MyString{
|
||||||
|
data: 'yes'
|
||||||
|
},
|
||||||
|
MyString{
|
||||||
|
data: 'default value'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_null() {
|
||||||
|
assert json.decode[json2.Any]('null]')! == json2.Any(json2.null)
|
||||||
|
assert json.decode[json2.Any]('{"hi": 90, "bye": ["lol", -1, null]}')!.str() == '{"hi":90,"bye":["lol",-1,null]}'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_big() {
|
||||||
|
assert json.decode[big.Integer]('0')!.str() == '0'
|
||||||
|
|
||||||
|
assert json.decode[big.Integer]('12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890')!.str() == '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'
|
||||||
|
|
||||||
|
assert json.decode[big.Integer]('-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890')!.str() == '-12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'
|
||||||
|
}
|
@ -41,6 +41,9 @@ pub struct Null {
|
|||||||
// null is an instance of the Null type, to ease comparisons with it.
|
// null is an instance of the Null type, to ease comparisons with it.
|
||||||
pub const null = Null{}
|
pub const null = Null{}
|
||||||
|
|
||||||
|
// from_json_null implements a custom decoder for json2
|
||||||
|
pub fn (mut n Null) from_json_null() {}
|
||||||
|
|
||||||
// ValueKind enumerates the kinds of possible values of the Any sumtype.
|
// ValueKind enumerates the kinds of possible values of the Any sumtype.
|
||||||
pub enum ValueKind {
|
pub enum ValueKind {
|
||||||
unknown
|
unknown
|
||||||
|
Loading…
x
Reference in New Issue
Block a user