diff --git a/vlib/math/big/json_decode.v b/vlib/math/big/json_decode.v new file mode 100644 index 0000000000..cc2d89adad --- /dev/null +++ b/vlib/math/big/json_decode.v @@ -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) + } +} diff --git a/vlib/time/json_decode.c.v b/vlib/time/json_decode.c.v new file mode 100644 index 0000000000..f4293d8420 --- /dev/null +++ b/vlib/time/json_decode.c.v @@ -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{} } +} diff --git a/vlib/x/json2/decoder2/custom.v b/vlib/x/json2/decoder2/custom.v new file mode 100644 index 0000000000..a6d9477074 --- /dev/null +++ b/vlib/x/json2/decoder2/custom.v @@ -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)! +// } diff --git a/vlib/x/json2/decoder2/decode.v b/vlib/x/json2/decoder2/decode.v index c48ab00095..fc7c342590 100644 --- a/vlib/x/json2/decoder2/decode.v +++ b/vlib/x/json2/decoder2/decode.v @@ -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]{} diff --git a/vlib/x/json2/decoder2/decode_sumtype.v b/vlib/x/json2/decoder2/decode_sumtype.v index c31b4adc9d..6f008eb691 100644 --- a/vlib/x/json2/decoder2/decode_sumtype.v +++ b/vlib/x/json2/decoder2/decode_sumtype.v @@ -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 } } } diff --git a/vlib/x/json2/decoder2/tests/decode_custom_test.v b/vlib/x/json2/decoder2/tests/decode_custom_test.v new file mode 100644 index 0000000000..80c3afb4df --- /dev/null +++ b/vlib/x/json2/decoder2/tests/decode_custom_test.v @@ -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' +} diff --git a/vlib/x/json2/types.v b/vlib/x/json2/types.v index 58dd1c386b..19f52d053f 100644 --- a/vlib/x/json2/types.v +++ b/vlib/x/json2/types.v @@ -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