decoder2: support custom decoders (#25021)

This commit is contained in:
Larsimusrex 2025-08-02 13:07:29 +02:00 committed by GitHub
parent b628626923
commit 809e617501
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 243 additions and 12 deletions

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

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

View 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)!
// }

View File

@ -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]{}

View File

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

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

View File

@ -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