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
|
||||
|
||||
import strconv
|
||||
import time
|
||||
import strings
|
||||
|
||||
const null_in_string = 'null'
|
||||
@ -533,8 +532,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
|
||||
}
|
||||
.number {
|
||||
// check if the JSON string is a valid float or integer
|
||||
|
||||
if val[0] == `-` {
|
||||
if val[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 {
|
||||
decoder.decode_sumtype(mut val)!
|
||||
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 {
|
||||
decoder.decode_map(mut val)!
|
||||
return
|
||||
@ -811,6 +800,50 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
|
||||
} $else $if T.unaliased_typ is $struct {
|
||||
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
|
||||
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
|
||||
} $else $if element is time.Time {
|
||||
return true
|
||||
} $else $if element is StringDecoder {
|
||||
return true
|
||||
}
|
||||
}
|
||||
.number {
|
||||
@ -56,16 +58,22 @@ fn (mut decoder Decoder) check_element_type_valid[T](element T, current_node &No
|
||||
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 {
|
||||
@ -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 {
|
||||
val = T(v)
|
||||
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 {
|
||||
val = T(v)
|
||||
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 {
|
||||
val = T(v)
|
||||
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 {
|
||||
val = T(v)
|
||||
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.
|
||||
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.
|
||||
pub enum ValueKind {
|
||||
unknown
|
||||
|
Loading…
x
Reference in New Issue
Block a user