Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,232 @@
|
||||
> The name `json2` was chosen to avoid any unwanted potential conflicts with the
|
||||
> existing codegen tailored for the main `json` module which is powered by CJSON.
|
||||
|
||||
`x.json2` is an experimental JSON parser written from scratch on V.
|
||||
|
||||
## Usage
|
||||
|
||||
#### encode[T]
|
||||
|
||||
```v
|
||||
import x.json2
|
||||
import time
|
||||
|
||||
struct Person {
|
||||
mut:
|
||||
name string
|
||||
age ?int = 20
|
||||
birthday time.Time
|
||||
deathday ?time.Time
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut person := Person{
|
||||
name: 'Bob'
|
||||
birthday: time.now()
|
||||
}
|
||||
person_json := json2.encode[Person](person)
|
||||
// person_json == {"name": "Bob", "age": 20, "birthday": "2022-03-11T13:54:25.000Z"}
|
||||
}
|
||||
```
|
||||
|
||||
Enums encode as strings by default. Use `@[json_as_number]` on an enum to emit
|
||||
its integer value instead.
|
||||
|
||||
#### decode[T]
|
||||
|
||||
```v
|
||||
import x.json2
|
||||
import time
|
||||
|
||||
struct Person {
|
||||
mut:
|
||||
name string
|
||||
age ?int = 20
|
||||
birthday time.Time
|
||||
deathday ?time.Time
|
||||
}
|
||||
|
||||
fn main() {
|
||||
resp := '{"name": "Bob", "age": 20, "birthday": "${time.now()}"}'
|
||||
person := json2.decode[Person](resp)!
|
||||
// struct Person {
|
||||
// mut:
|
||||
// name "Bob"
|
||||
// age 20
|
||||
// birthday "2022-03-11 13:54:25"
|
||||
// deathday "2022-03-11 13:54:25"
|
||||
// }
|
||||
}
|
||||
```
|
||||
|
||||
decode[T] is smart and can auto-convert the types of struct fields - this means
|
||||
examples below will have the same result
|
||||
|
||||
Embedded struct fields are decoded from the surrounding object, including reference fields.
|
||||
|
||||
```v ignore
|
||||
json2.decode[Person]('{"name": "Bob", "age": 20, "birthday": "2022-03-11T13:54:25.000Z"}')!
|
||||
json2.decode[Person]('{"name": "Bob", "age": 20, "birthday": "2022-03-11 13:54:25.000"}')!
|
||||
json2.decode[Person]('{"name": "Bob", "age": "20", "birthday": 1647006865}')!
|
||||
json2.decode[Person]('{"name": "Bob", "age": "20", "birthday": "1647006865"}}')!
|
||||
```
|
||||
|
||||
#### raw decode
|
||||
|
||||
```v
|
||||
import x.json2
|
||||
import net.http
|
||||
|
||||
fn main() {
|
||||
resp := http.get('https://reqres.in/api/products/1')!
|
||||
|
||||
// This returns an Any type
|
||||
raw_product := json2.decode[json2.Any](resp.body)!
|
||||
}
|
||||
```
|
||||
|
||||
#### iterative token scanning
|
||||
|
||||
`x.json2` now exposes low-level scanners that let you process JSON token by
|
||||
token instead of materializing the whole tree first.
|
||||
|
||||
Use `new_scanner()` for in-memory strings:
|
||||
|
||||
```v
|
||||
import x.json2
|
||||
|
||||
fn main() {
|
||||
mut scanner := json2.new_scanner('{"items":[1,2,3]}')
|
||||
for {
|
||||
token := scanner.next()!
|
||||
if token.is_eof() {
|
||||
break
|
||||
}
|
||||
println('${token.kind}: ${token.literal()}')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Use `new_reader_scanner()` to stream tokens from a file or any `io.Reader`:
|
||||
|
||||
```v
|
||||
import os
|
||||
import x.json2
|
||||
|
||||
fn main() {
|
||||
mut file := os.open('huge.json')!
|
||||
defer {
|
||||
file.close()
|
||||
}
|
||||
|
||||
mut scanner := json2.new_reader_scanner(reader: file)
|
||||
defer {
|
||||
scanner.free()
|
||||
}
|
||||
|
||||
for {
|
||||
token := scanner.next()!
|
||||
if token.is_eof() {
|
||||
break
|
||||
}
|
||||
if token.kind == .str && token.literal() == 'id' {
|
||||
println('found an id key')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Casting `Any` type / Navigating
|
||||
|
||||
```v
|
||||
import x.json2
|
||||
import net.http
|
||||
|
||||
fn main() {
|
||||
resp := http.get('https://reqres.in/api/products/1')!
|
||||
|
||||
raw_product := json2.decode[json2.Any](resp.body)!
|
||||
|
||||
product := raw_product.as_map()
|
||||
data := product['data'] as map[string]json2.Any
|
||||
|
||||
id := data['id'].int() // 1
|
||||
name := data['name'].str() // cerulean
|
||||
year := data['year'].int() // 2000
|
||||
}
|
||||
```
|
||||
|
||||
#### Constructing an `Any` type
|
||||
|
||||
```v
|
||||
import x.json2
|
||||
|
||||
fn main() {
|
||||
mut me := map[string]json2.Any{}
|
||||
me['name'] = 'Bob'
|
||||
me['age'] = 18
|
||||
|
||||
mut arr := []json2.Any{}
|
||||
arr << 'rock'
|
||||
arr << 'papers'
|
||||
arr << json2.null
|
||||
arr << 12
|
||||
|
||||
me['interests'] = arr
|
||||
|
||||
mut pets := map[string]json2.Any{}
|
||||
pets['Sam'] = 'Maltese Shitzu'
|
||||
me['pets'] = pets
|
||||
|
||||
// Stringify to JSON
|
||||
println(me.str())
|
||||
//{
|
||||
// "name":"Bob",
|
||||
// "age":18,
|
||||
// "interests":["rock","papers","scissors",null,12],
|
||||
// "pets":{"Sam":"Maltese"}
|
||||
//}
|
||||
}
|
||||
```
|
||||
|
||||
### Null Values
|
||||
|
||||
`x.json2` has a separate `Null` type for differentiating an undefined value and a null value.
|
||||
To verify that the field you're accessing is a `Null`, use `[typ] is json2.Null`.
|
||||
|
||||
```v ignore
|
||||
fn (mut p Person) from_json(f json2.Any) {
|
||||
obj := f.as_map()
|
||||
if obj['age'] is json2.Null {
|
||||
// use a default value
|
||||
p.age = 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Casting a value to an incompatible type
|
||||
|
||||
`x.json2` provides methods for turning `Any` types into usable types.
|
||||
The following list shows the possible outputs when casting a value to an incompatible type.
|
||||
|
||||
1. Casting non-array values with `as_array()` will return an array with the value as the content.
|
||||
2. Casting non-map values as map (`as_map()`) will return a map with the value as the content.
|
||||
3. Casting non-string values to string (`str()`) will return the
|
||||
JSON string representation of the value.
|
||||
4. Casting non-numeric values to int/float (`int()`/`i64()`/`f32()`/`f64()`) will return zero.
|
||||
|
||||
## Encoding using string builder instead of []u8
|
||||
|
||||
To be more performant, `json2`, in PR 20052, decided to use buffers directly instead of Writers.
|
||||
If you want to use Writers you can follow the steps below:
|
||||
|
||||
```v ignore
|
||||
mut sb := strings.new_builder(64)
|
||||
mut buffer := []u8{}
|
||||
|
||||
json2.encode_value(<some value to be encoded here>, mut buffer)!
|
||||
|
||||
sb.write(buffer)!
|
||||
|
||||
unsafe { buffer.free() }
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
module json2
|
||||
|
||||
@[inline]
|
||||
fn unquote_attr_value(value string) string {
|
||||
mut unquoted := value.trim_space()
|
||||
if unquoted.len > 1 {
|
||||
if (unquoted[0] == `'` && unquoted[unquoted.len - 1] == `'`)
|
||||
|| (unquoted[0] == `"` && unquoted[unquoted.len - 1] == `"`) {
|
||||
unquoted = unquoted[1..unquoted.len - 1]
|
||||
}
|
||||
}
|
||||
return unquoted
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn json_attr_value(attr string) ?string {
|
||||
if !attr.starts_with('json:') {
|
||||
return none
|
||||
}
|
||||
return unquote_attr_value(attr[5..])
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn json_attr_value_range(attr string) ?(int, int) {
|
||||
if !attr.starts_with('json:') {
|
||||
return none
|
||||
}
|
||||
mut start := 5
|
||||
for start < attr.len && attr[start] in [` `, `\t`, `\n`, `\r`] {
|
||||
start++
|
||||
}
|
||||
mut end := attr.len
|
||||
for end > start && attr[end - 1] in [` `, `\t`, `\n`, `\r`] {
|
||||
end--
|
||||
}
|
||||
if end - start > 1 {
|
||||
if (attr[start] == `'` && attr[end - 1] == `'`)
|
||||
|| (attr[start] == `"` && attr[end - 1] == `"`) {
|
||||
start++
|
||||
end--
|
||||
}
|
||||
}
|
||||
return start, end
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn enum_uses_json_as_number[T]() bool {
|
||||
$for attr in T.attributes {
|
||||
if attr.name == 'json_as_number' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
module json2
|
||||
|
||||
// increment checks eof and increments checker by one
|
||||
@[inline; markused]
|
||||
fn (mut checker Decoder) increment(message string) ! {
|
||||
if checker.checker_idx + 1 == checker.json.len {
|
||||
if message == '' {
|
||||
return Error{}
|
||||
}
|
||||
checker.checker_error('EOF: ' + message)!
|
||||
}
|
||||
checker.checker_idx++
|
||||
}
|
||||
|
||||
// skip_whitespace checks eof and increments checker until next non whitespace character
|
||||
@[inline; markused]
|
||||
fn (mut checker Decoder) skip_whitespace(message string) ! {
|
||||
for checker.json[checker.checker_idx] in whitespace_chars {
|
||||
checker.increment(message)!
|
||||
}
|
||||
}
|
||||
|
||||
// check_json_format checks if the JSON string is valid and updates the decoder state.
|
||||
@[markused]
|
||||
fn (mut checker Decoder) check_json_format() ! {
|
||||
checker.skip_whitespace('empty json')!
|
||||
|
||||
start_idx_position := checker.checker_idx
|
||||
|
||||
mut actual_value_info_pointer := unsafe { &ValueInfo(nil) }
|
||||
|
||||
match checker.json[checker.checker_idx] {
|
||||
`"` {
|
||||
checker.values_info.push(ValueInfo{
|
||||
position: checker.checker_idx
|
||||
value_kind: .string
|
||||
})
|
||||
|
||||
actual_value_info_pointer = checker.values_info.last()
|
||||
|
||||
checker.check_string()!
|
||||
}
|
||||
`-`, `0`...`9` {
|
||||
checker.values_info.push(ValueInfo{
|
||||
position: checker.checker_idx
|
||||
value_kind: .number
|
||||
})
|
||||
|
||||
actual_value_info_pointer = checker.values_info.last()
|
||||
|
||||
checker.check_number()!
|
||||
}
|
||||
`t`, `f` {
|
||||
checker.values_info.push(ValueInfo{
|
||||
position: checker.checker_idx
|
||||
value_kind: .boolean
|
||||
})
|
||||
|
||||
actual_value_info_pointer = checker.values_info.last()
|
||||
|
||||
checker.check_boolean()!
|
||||
}
|
||||
`n` {
|
||||
checker.values_info.push(ValueInfo{
|
||||
position: checker.checker_idx
|
||||
value_kind: .null
|
||||
})
|
||||
|
||||
actual_value_info_pointer = checker.values_info.last()
|
||||
|
||||
checker.check_null()!
|
||||
}
|
||||
`[` {
|
||||
checker.values_info.push(ValueInfo{
|
||||
position: checker.checker_idx
|
||||
value_kind: .array
|
||||
})
|
||||
|
||||
actual_value_info_pointer = checker.values_info.last()
|
||||
|
||||
checker.check_array()!
|
||||
}
|
||||
`{` {
|
||||
checker.values_info.push(ValueInfo{
|
||||
position: checker.checker_idx
|
||||
value_kind: .object
|
||||
})
|
||||
|
||||
actual_value_info_pointer = checker.values_info.last()
|
||||
|
||||
checker.check_object()!
|
||||
}
|
||||
else {
|
||||
checker.checker_error('unknown value kind')!
|
||||
}
|
||||
}
|
||||
|
||||
actual_value_info_pointer.length = checker.checker_idx + 1 - start_idx_position
|
||||
|
||||
checker.increment('') or { return }
|
||||
checker.skip_whitespace('') or { return }
|
||||
|
||||
if checker.json[checker.checker_idx] !in [`,`, `:`, `}`, `]`] {
|
||||
checker.checker_error('invalid value. Unexpected character after ${actual_value_info_pointer.value_kind} end')!
|
||||
}
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut checker Decoder) check_string() ! {
|
||||
checker.increment('string not closed')!
|
||||
|
||||
// check if the JSON string is a valid escape sequence
|
||||
for checker.json[checker.checker_idx] != `"` {
|
||||
if checker.json[checker.checker_idx] == `\\` {
|
||||
checker.increment('invalid escape sequence')!
|
||||
escaped_char := checker.json[checker.checker_idx]
|
||||
match escaped_char {
|
||||
`/`, `b`, `f`, `n`, `r`, `t`, `"`, `\\` {}
|
||||
`u` {
|
||||
// check if the JSON string is a valid unicode escape sequence
|
||||
escaped_char_last_index := checker.checker_idx + 4
|
||||
|
||||
if escaped_char_last_index < checker.json.len {
|
||||
// 2 bytes for the unicode escape sequence `\u`
|
||||
checker.increment('invalid escape sequence')!
|
||||
|
||||
for checker.checker_idx < escaped_char_last_index {
|
||||
match checker.json[checker.checker_idx] {
|
||||
`0`...`9`, `a`...`f`, `A`...`F` {
|
||||
checker.increment('invalid unicode escape sequence')!
|
||||
}
|
||||
else {
|
||||
return checker.checker_error('invalid unicode escape sequence')
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
return checker.checker_error('short unicode escape sequence ${checker.json[checker.checker_idx - 1..checker.json.len - 1]}')
|
||||
}
|
||||
}
|
||||
else {
|
||||
return checker.checker_error('unknown escape sequence')
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.increment('string not closed')!
|
||||
}
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut checker Decoder) check_number() ! {
|
||||
// check if the JSON string is a valid float or integer
|
||||
if checker.json[checker.checker_idx] == `-` {
|
||||
checker.increment('expected digit')!
|
||||
}
|
||||
|
||||
// integer part
|
||||
if checker.json[checker.checker_idx] == `0` {
|
||||
checker.increment('') or { return }
|
||||
} else if checker.json[checker.checker_idx] >= `1` && checker.json[checker.checker_idx] <= `9` {
|
||||
checker.increment('') or { return }
|
||||
|
||||
for checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` {
|
||||
checker.increment('') or { return }
|
||||
}
|
||||
} else {
|
||||
return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}')
|
||||
}
|
||||
|
||||
// fraction part
|
||||
if checker.json[checker.checker_idx] == `.` {
|
||||
checker.increment('expected digit')!
|
||||
|
||||
if !(checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9`) {
|
||||
return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}')
|
||||
}
|
||||
|
||||
for checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` {
|
||||
checker.increment('') or { return }
|
||||
}
|
||||
}
|
||||
|
||||
// exponent part
|
||||
if checker.json[checker.checker_idx] == `e` || checker.json[checker.checker_idx] == `E` {
|
||||
checker.increment('expected digit')!
|
||||
|
||||
if checker.json[checker.checker_idx] == `-` || checker.json[checker.checker_idx] == `+` {
|
||||
checker.increment('expected digit')!
|
||||
}
|
||||
|
||||
if !(checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9`) {
|
||||
return checker.checker_error('expected digit got ${checker.json[checker.checker_idx].ascii_str()}')
|
||||
}
|
||||
|
||||
for checker.json[checker.checker_idx] >= `0` && checker.json[checker.checker_idx] <= `9` {
|
||||
checker.increment('') or { return }
|
||||
}
|
||||
}
|
||||
|
||||
checker.checker_idx--
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut checker Decoder) check_boolean() ! {
|
||||
// check if the JSON string is a valid boolean
|
||||
match checker.json[checker.checker_idx] {
|
||||
`t` {
|
||||
if checker.json.len - checker.checker_idx <= 3 {
|
||||
return checker.checker_error('EOF error: expecting `true`')
|
||||
}
|
||||
|
||||
is_not_ok := unsafe {
|
||||
vmemcmp(checker.json.str + checker.checker_idx, c'true', 'true'.len)
|
||||
}
|
||||
|
||||
if is_not_ok != 0 {
|
||||
return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx..
|
||||
checker.checker_idx + 4]}` instead of `true`')
|
||||
}
|
||||
checker.checker_idx += 3
|
||||
}
|
||||
`f` {
|
||||
if checker.json.len - checker.checker_idx <= 4 {
|
||||
return checker.checker_error('EOF error: expecting `false`')
|
||||
}
|
||||
|
||||
is_not_ok := unsafe {
|
||||
vmemcmp(checker.json.str + checker.checker_idx, c'false', 'false'.len)
|
||||
}
|
||||
|
||||
if is_not_ok != 0 {
|
||||
return checker.checker_error('invalid boolean value. Got `${checker.json[checker.checker_idx..
|
||||
checker.checker_idx + 5]}` instead of `false`')
|
||||
}
|
||||
|
||||
checker.checker_idx += 4
|
||||
}
|
||||
else {
|
||||
return checker.checker_error('invalid boolean')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut checker Decoder) check_null() ! {
|
||||
// check if the JSON string is a null value
|
||||
if checker.json.len - checker.checker_idx <= 3 {
|
||||
return checker.checker_error('EOF error: expecting `null`')
|
||||
}
|
||||
|
||||
is_not_ok := unsafe {
|
||||
vmemcmp(checker.json.str + checker.checker_idx, c'null', 'null'.len)
|
||||
}
|
||||
|
||||
if is_not_ok != 0 {
|
||||
return checker.checker_error('invalid null value. Got `${checker.json[checker.checker_idx..
|
||||
checker.checker_idx + 4]}` instead of `null`')
|
||||
}
|
||||
checker.checker_idx += 3
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut checker Decoder) check_array() ! {
|
||||
checker.increment('expected array end')!
|
||||
|
||||
checker.skip_whitespace('expected array end')!
|
||||
|
||||
for checker.json[checker.checker_idx] != `]` {
|
||||
checker.check_json_format()!
|
||||
|
||||
checker.skip_whitespace('expected array end')!
|
||||
|
||||
if checker.json[checker.checker_idx] == `,` {
|
||||
checker.increment('expected array value')!
|
||||
checker.skip_whitespace('') or {}
|
||||
|
||||
if checker.json[checker.checker_idx] == `]` {
|
||||
return checker.checker_error('Cannot use `,`, before `]`')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut checker Decoder) check_object() ! {
|
||||
checker.increment('expected object end')!
|
||||
|
||||
checker.skip_whitespace('expected object end')!
|
||||
|
||||
for checker.json[checker.checker_idx] != `}` {
|
||||
if checker.json[checker.checker_idx] != `"` {
|
||||
checker.checker_error('Expecting object key')!
|
||||
}
|
||||
|
||||
checker.check_json_format()!
|
||||
|
||||
checker.skip_whitespace('expected `:`')!
|
||||
|
||||
if checker.json[checker.checker_idx] != `:` {
|
||||
checker.checker_error('expected `:`, got `${checker.json[checker.checker_idx].ascii_str()}`')!
|
||||
}
|
||||
|
||||
checker.increment('expected object value')!
|
||||
|
||||
checker.skip_whitespace('expected object value')!
|
||||
|
||||
checker.check_json_format()!
|
||||
|
||||
checker.skip_whitespace('expected object end')!
|
||||
|
||||
if checker.json[checker.checker_idx] == `,` {
|
||||
checker.increment('expected object key')!
|
||||
checker.skip_whitespace('') or {}
|
||||
|
||||
if checker.json[checker.checker_idx] == `}` {
|
||||
return checker.checker_error('Cannot use `,`, before `}`')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module json2
|
||||
|
||||
const true_string = 'true'
|
||||
const false_string = 'false'
|
||||
const null_string = 'null'
|
||||
@@ -0,0 +1,109 @@
|
||||
module json2
|
||||
|
||||
import time
|
||||
|
||||
struct Count {
|
||||
mut:
|
||||
total int
|
||||
}
|
||||
|
||||
// get_total
|
||||
fn (mut count Count) get_total() int {
|
||||
return count.total
|
||||
}
|
||||
|
||||
// reset_total
|
||||
fn (mut count Count) reset_total() {
|
||||
count.total = 0
|
||||
}
|
||||
|
||||
// count_chars count json sizen without new encode
|
||||
fn (mut count Count) count_chars[T](val T) {
|
||||
$if val is $option {
|
||||
workaround := val
|
||||
if workaround != none {
|
||||
count.count_chars(val)
|
||||
}
|
||||
} $else $if T is string {
|
||||
count.chars_in_string(val)
|
||||
} $else $if T is $sumtype {
|
||||
$for v in val.variants {
|
||||
if val is v {
|
||||
count.count_chars(val)
|
||||
}
|
||||
}
|
||||
} $else $if T is $alias {
|
||||
// TODO
|
||||
} $else $if T is time.Time {
|
||||
count.total += 26 // "YYYY-MM-DDTHH:mm:ss.123Z"
|
||||
} $else $if T is $map {
|
||||
count.total++ // {
|
||||
for k, v in val {
|
||||
count.count_chars(k)
|
||||
count.total++ // :
|
||||
count.count_chars(v)
|
||||
}
|
||||
count.total++ // }
|
||||
} $else $if T is $array {
|
||||
count.total += 2 // []
|
||||
if val.len > 0 {
|
||||
for element in val {
|
||||
count.count_chars(element)
|
||||
}
|
||||
count.total += val.len - 1 // ,
|
||||
}
|
||||
} $else $if T is $struct {
|
||||
count.chars_in_struct(val)
|
||||
} $else $if T is $enum {
|
||||
mut enum_name := 'unknown enum value'
|
||||
$for member in T.values {
|
||||
if member.value == val {
|
||||
enum_name = member.name
|
||||
for attr in member.attrs {
|
||||
if json_attr := json_attr_value(attr) {
|
||||
enum_name = json_attr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
count.total += enum_name.len + 2 // ""
|
||||
} $else $if T is $int {
|
||||
// TODO: benchmark
|
||||
mut abs_val := val
|
||||
if val < 0 {
|
||||
count.total++ // -
|
||||
abs_val = -val
|
||||
}
|
||||
for number_value := abs_val; number_value >= 1; number_value /= 10 {
|
||||
count.total++
|
||||
}
|
||||
if val == 0 {
|
||||
count.total++
|
||||
}
|
||||
} $else $if T is $float {
|
||||
// TODO
|
||||
} $else $if T is bool {
|
||||
if val {
|
||||
count.total += 4 // true
|
||||
} else {
|
||||
count.total += 5 // false
|
||||
}
|
||||
} $else {
|
||||
}
|
||||
}
|
||||
|
||||
// chars_in_struct
|
||||
fn (mut count Count) chars_in_struct[T](val T) {
|
||||
count.total += 2 // {}
|
||||
$for field in T.fields {
|
||||
// TODO: handle attributes
|
||||
count.total += field.name.len + 3 // "":
|
||||
workaround := val.$(field.name)
|
||||
count.count_chars(workaround)
|
||||
}
|
||||
}
|
||||
|
||||
// chars_in_string
|
||||
fn (mut count Count) chars_in_string(val string) {
|
||||
count.total += val.len + 2 // ""
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
module json2
|
||||
|
||||
// implements encoding json, this is not validated so implementations must be correct
|
||||
pub interface JsonEncoder {
|
||||
// to_json returns a string containing an objects json representation
|
||||
to_json() string
|
||||
}
|
||||
|
||||
// Encodable is an interface, that allows custom implementations for encoding structs to their string based JSON representations.
|
||||
|
||||
@[deprecated: 'use `to_json` to implement `JsonEncoder` instead']
|
||||
@[deprecated_after: '2025-10-30']
|
||||
pub interface Encodable {
|
||||
json_str() string
|
||||
}
|
||||
|
||||
// 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)!
|
||||
// }
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,413 @@
|
||||
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('.')
|
||||
}
|
||||
|
||||
fn copy_type[T](_t T) T {
|
||||
return T{}
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T) !T {
|
||||
$if initialized_sumtype is $sumtype || (T is $alias && T.unaliased_typ is $sumtype) {
|
||||
resolved_sumtype := initialized_sumtype
|
||||
$for v in T.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is time.Time {
|
||||
mut val := copy_type(initialized_sumtype)
|
||||
decoder.decode_sumtype_time(mut val)!
|
||||
return T(val)
|
||||
} $else $if initialized_sumtype !is $option {
|
||||
mut val := copy_type(initialized_sumtype)
|
||||
decoder.decode_value(mut val)!
|
||||
return T(val)
|
||||
} $else {
|
||||
if decoder.current_node.value.value_kind == .null {
|
||||
decoder.current_node = decoder.current_node.next
|
||||
return resolved_sumtype
|
||||
} else {
|
||||
decoder.decode_error('sumtype option only support decoding null->none (for now)')!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
decoder.decode_error('could not decode resolved sumtype (should not happen)')!
|
||||
return initialized_sumtype // suppress compiler error
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_element_type_valid[T](element T, current_node &Node[ValueInfo]) bool {
|
||||
if current_node == unsafe { nil } {
|
||||
$if element is $array || element is $map {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
$if element is $sumtype { // this will always match the first sumtype array/map
|
||||
return true
|
||||
}
|
||||
|
||||
match current_node.value.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, current_node.next)
|
||||
}
|
||||
}
|
||||
.object {
|
||||
$if element is $map {
|
||||
if current_node.next != unsafe { nil } {
|
||||
return decoder.check_map_type_valid(element, current_node.next.next)
|
||||
} else {
|
||||
return decoder.check_map_type_valid(element, unsafe { nil })
|
||||
}
|
||||
} $else $if element is $struct {
|
||||
return decoder.check_struct_type_valid(element, current_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fn get_array_element_type[T](_arr []T) T {
|
||||
return T{}
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_array_type_valid[T](arr []T, current_node &Node[ValueInfo]) bool {
|
||||
element := get_array_element_type(arr)
|
||||
return decoder.check_element_type_valid(element, current_node)
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_array_type_workaround[T](initialized_sumtype T) bool {
|
||||
$if initialized_sumtype is $sumtype || (T is $alias && T.unaliased_typ is $sumtype) {
|
||||
$for v in T.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is $array {
|
||||
return decoder.check_element_type_valid(initialized_sumtype,
|
||||
decoder.current_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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, current_node &Node[ValueInfo]) bool {
|
||||
element := get_map_element_type(m)
|
||||
return decoder.check_element_type_valid(element, current_node)
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_map_empty_valid[T](m T) bool {
|
||||
element := get_map_element_type(m)
|
||||
return decoder.check_element_type_valid(element, current_node)
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_map_type_workaround[T](initialized_sumtype T) bool {
|
||||
$if initialized_sumtype is $sumtype || (T is $alias && T.unaliased_typ is $sumtype) {
|
||||
$for v in T.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is $map {
|
||||
val := copy_type(initialized_sumtype)
|
||||
if decoder.current_node.next != unsafe { nil } {
|
||||
return decoder.check_map_type_valid(val, decoder.current_node.next.next)
|
||||
} else {
|
||||
return decoder.check_map_type_valid(val, unsafe { nil })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (decoder &Decoder) get_sumtype_type_field_node(current_node &Node[ValueInfo]) &Node[ValueInfo] {
|
||||
if current_node == unsafe { nil } {
|
||||
return unsafe { nil }
|
||||
}
|
||||
|
||||
// find "_type" field in json object
|
||||
mut type_field_node := current_node.next
|
||||
map_position := current_node.value.position
|
||||
map_end := map_position + current_node.value.length
|
||||
|
||||
type_field := '"_type"'
|
||||
|
||||
for {
|
||||
if type_field_node == unsafe { nil } {
|
||||
break
|
||||
}
|
||||
|
||||
key_info := type_field_node.value
|
||||
|
||||
if key_info.position >= map_end {
|
||||
type_field_node = unsafe { nil }
|
||||
break
|
||||
}
|
||||
|
||||
if unsafe {
|
||||
key_info.position + type_field.len <= decoder.json.len
|
||||
&& 0 == vmemcmp(decoder.json.str + key_info.position, type_field.str, type_field.len)
|
||||
} {
|
||||
// find type field
|
||||
return type_field_node.next
|
||||
} else {
|
||||
type_field_node = type_field_node.next
|
||||
}
|
||||
}
|
||||
|
||||
return unsafe { nil }
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (decoder &Decoder) sumtype_type_field_matches(type_field_node &Node[ValueInfo], expected string) bool {
|
||||
if type_field_node == unsafe { nil } {
|
||||
return false
|
||||
}
|
||||
if type_field_node.value.value_kind != .string {
|
||||
return false
|
||||
}
|
||||
if type_field_node.value.length - 2 != expected.len {
|
||||
return false
|
||||
}
|
||||
return unsafe {
|
||||
type_field_node.value.position + 1 + expected.len <= decoder.json.len
|
||||
&& 0 == vmemcmp(decoder.json.str + type_field_node.value.position + 1, expected.str, expected.len)
|
||||
}
|
||||
}
|
||||
|
||||
fn (decoder &Decoder) check_sumtype_type_valid[T](value T, current_node &Node[ValueInfo]) bool {
|
||||
type_field_node := decoder.get_sumtype_type_field_node(current_node)
|
||||
return decoder.sumtype_type_field_matches(type_field_node,
|
||||
sumtype_variant_name(typeof(value).name))
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) check_struct_type_valid[T](s T, current_node &Node[ValueInfo]) bool {
|
||||
return decoder.check_sumtype_type_valid(s, current_node)
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_struct_type_workaround[T](initialized_sumtype T) bool {
|
||||
$if initialized_sumtype is $sumtype || (T is $alias && T.unaliased_typ is $sumtype) {
|
||||
$for v in T.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is $struct {
|
||||
val := copy_type(initialized_sumtype)
|
||||
return decoder.check_struct_type_valid(val, decoder.current_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) get_time_type_workaround[T](initialized_sumtype T) bool {
|
||||
$if initialized_sumtype is $sumtype || (T is $alias && T.unaliased_typ is $sumtype) {
|
||||
$for v in T.variants {
|
||||
if initialized_sumtype is v {
|
||||
$if initialized_sumtype is time.Time {
|
||||
val := copy_type(initialized_sumtype)
|
||||
return decoder.check_sumtype_type_valid(val, decoder.current_node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) resolve_sumtype_from_type_field[T](mut val T) !bool {
|
||||
if decoder.get_sumtype_type_field_node(decoder.current_node) == unsafe { nil } {
|
||||
return false
|
||||
}
|
||||
mut has_discriminated_variant := false
|
||||
$for v in T.variants {
|
||||
$if v.typ is time.Time {
|
||||
has_discriminated_variant = true
|
||||
val = T(v)
|
||||
if decoder.get_time_type_workaround(val) {
|
||||
return true
|
||||
}
|
||||
} $else $if v.typ is $struct {
|
||||
has_discriminated_variant = true
|
||||
val = T(v)
|
||||
if decoder.get_struct_type_workaround(val) {
|
||||
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
|
||||
|
||||
match value_info.value_kind {
|
||||
.string {
|
||||
$for v in T.variants {
|
||||
$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 v in T.variants {
|
||||
$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 v in T.variants {
|
||||
$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 {
|
||||
val = T(v)
|
||||
|
||||
if decoder.get_array_type_workaround(val) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.object {
|
||||
if decoder.resolve_sumtype_from_type_field(mut val)! {
|
||||
return
|
||||
}
|
||||
$for v in T.variants {
|
||||
$if v.typ is $map {
|
||||
val = T(v)
|
||||
|
||||
if decoder.get_map_type_workaround(val) {
|
||||
return
|
||||
}
|
||||
} $else $if v.typ is $struct {
|
||||
struct_variant_count++
|
||||
val = T(v)
|
||||
|
||||
if decoder.get_struct_type_workaround(val) {
|
||||
return
|
||||
}
|
||||
|
||||
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_node(decoder.current_node) == unsafe { nil } {
|
||||
return
|
||||
}
|
||||
decoder.decode_error('could not resolve sumtype `${T.name}`, missing "_type" field?')!
|
||||
}
|
||||
|
||||
decoder.decode_error('could not resolve sumtype `${T.name}`, got ${value_info.value_kind}.')!
|
||||
}
|
||||
|
||||
fn (mut decoder Decoder) decode_sumtype[T](mut val T) ! {
|
||||
value_info := decoder.current_node.value
|
||||
|
||||
decoder.init_sumtype_by_value_kind(mut val, value_info)!
|
||||
|
||||
val = decoder.get_decoded_sumtype_workaround(val)!
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
module decoder2
|
||||
|
||||
import x.json2
|
||||
|
||||
// decode decodes a JSON string into a specified type.
|
||||
@[deprecated: '`decode` has been moved to `x.json2`, use `decode` from `x.json2` instead']
|
||||
@[deprecated_after: '2025-10-12']
|
||||
pub fn decode[T](val string) !T {
|
||||
return json2.decode[T](val)!
|
||||
}
|
||||
|
||||
// decode_array decodes a JSON string into a specified type. This is the same as decode.
|
||||
@[deprecated: '`decode_array` has been moved to `x.json2`, use `decode` from `x.json2` instead']
|
||||
@[deprecated_after: '2025-03-18']
|
||||
pub fn decode_array[T](src string) !T {
|
||||
return json2.decode[T](src)!
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module json2
|
||||
|
||||
// Decodes a JSON string into an `Any` type. Returns an option.
|
||||
@[deprecated: 'use `decode[json2.Any]` instead']
|
||||
@[deprecated_after: '2025-10-10']
|
||||
pub fn raw_decode(src string) !Any {
|
||||
return decode[Any](src)
|
||||
}
|
||||
@@ -0,0 +1,916 @@
|
||||
module json2
|
||||
|
||||
import time
|
||||
|
||||
// EncoderOptions provides a list of options for encoding
|
||||
@[params]
|
||||
pub struct EncoderOptions {
|
||||
pub:
|
||||
prettify bool
|
||||
indent_string string = ' '
|
||||
newline_string string = '\n'
|
||||
|
||||
enum_as_int bool
|
||||
|
||||
escape_unicode bool
|
||||
}
|
||||
|
||||
struct Encoder {
|
||||
EncoderOptions
|
||||
mut:
|
||||
level int
|
||||
prefix string
|
||||
|
||||
output []u8 = []u8{cap: 2048}
|
||||
}
|
||||
|
||||
// encode is a generic function that encodes a type into a JSON string.
|
||||
pub fn encode[T](val T, config EncoderOptions) string {
|
||||
mut encoder := Encoder{
|
||||
EncoderOptions: config
|
||||
}
|
||||
|
||||
encoder.encode_value[T](val)
|
||||
|
||||
return encoder.output.bytestr()
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_value[T](val T) {
|
||||
$if T is $interface {
|
||||
encoder.encode_null()
|
||||
} $else $if T.unaliased_typ is voidptr {
|
||||
encoder.encode_null()
|
||||
} $else $if T.unaliased_typ is string {
|
||||
encoder.encode_string(string(val))
|
||||
} $else $if T.unaliased_typ is bool {
|
||||
encoder.encode_boolean(bool(val))
|
||||
} $else $if T.unaliased_typ is u8 {
|
||||
encoder.encode_number(u8(val))
|
||||
} $else $if T.unaliased_typ is u16 {
|
||||
encoder.encode_number(u16(val))
|
||||
} $else $if T.unaliased_typ is u32 {
|
||||
encoder.encode_number(u32(val))
|
||||
} $else $if T.unaliased_typ is u64 {
|
||||
encoder.encode_number(u64(val))
|
||||
} $else $if T.unaliased_typ is i8 {
|
||||
encoder.encode_number(i8(val))
|
||||
} $else $if T.unaliased_typ is i16 {
|
||||
encoder.encode_number(i16(val))
|
||||
} $else $if T.unaliased_typ is int || T.unaliased_typ is i32 {
|
||||
encoder.encode_number(i32(val))
|
||||
} $else $if T.unaliased_typ is i64 {
|
||||
encoder.encode_number(i64(val))
|
||||
} $else $if T.unaliased_typ is usize {
|
||||
encoder.encode_number(usize(val))
|
||||
} $else $if T.unaliased_typ is isize {
|
||||
encoder.encode_number(isize(val))
|
||||
} $else $if T.unaliased_typ is f32 {
|
||||
encoder.encode_number(f32(val))
|
||||
} $else $if T.unaliased_typ is f64 {
|
||||
encoder.encode_number(f64(val))
|
||||
} $else $if T.unaliased_typ is voidptr {
|
||||
encoder.encode_number(0)
|
||||
} $else $if T is $pointer {
|
||||
if voidptr(val) == unsafe { nil } {
|
||||
encoder.encode_null()
|
||||
} else {
|
||||
encoder.encode_value(*val)
|
||||
}
|
||||
} $else $if T.unaliased_typ is $array_fixed {
|
||||
encoder.output << `[`
|
||||
for i in 0 .. val.len {
|
||||
encoder.encode_value(val[i])
|
||||
if i < val.len - 1 {
|
||||
encoder.output << `,`
|
||||
}
|
||||
}
|
||||
encoder.output << `]`
|
||||
} $else $if T.unaliased_typ is $array {
|
||||
encoder.encode_array(val)
|
||||
} $else $if T.unaliased_typ is $map {
|
||||
encoder.output << `{`
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
mut mi := 0
|
||||
for key, value in val {
|
||||
encoder.encode_string('${key}')
|
||||
encoder.output << `:`
|
||||
if encoder.prettify {
|
||||
encoder.output << ` `
|
||||
}
|
||||
encoder.encode_value(value)
|
||||
if mi < val.len - 1 {
|
||||
encoder.output << `,`
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
} else {
|
||||
if encoder.prettify {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
}
|
||||
mi++
|
||||
}
|
||||
encoder.output << `}`
|
||||
} $else $if T.unaliased_typ is $enum {
|
||||
if encoder.enum_as_int || enum_uses_json_as_number[T]() {
|
||||
encoder.encode_number(int(val))
|
||||
} else {
|
||||
mut enum_val := 'unknown enum value'
|
||||
$for member in T.values {
|
||||
if member.value == val {
|
||||
enum_val = member.name
|
||||
for attr in member.attrs {
|
||||
if json_attr := json_attr_value(attr) {
|
||||
enum_val = json_attr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
encoder.output << `"`
|
||||
unsafe { encoder.output.push_many(enum_val.str, enum_val.len) }
|
||||
encoder.output << `"`
|
||||
}
|
||||
} $else $if T.unaliased_typ is $sumtype {
|
||||
encoder.encode_sumtype[T](val)
|
||||
} $else $if T is JsonEncoder { // uses T, because alias could be implementing JsonEncoder, while the base type does not
|
||||
integer_val := val.to_json()
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
} $else $if T is Encodable { // uses T, because alias could be implementing JsonEncoder, while the base type does not
|
||||
integer_val := val.json_str()
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
} $else $if T.unaliased_typ is $struct {
|
||||
unsafe {
|
||||
$for field in T.fields {
|
||||
$if field.is_embed {
|
||||
encoder.encode_struct_with_embeds(val)
|
||||
return
|
||||
}
|
||||
}
|
||||
encoder.output << `{`
|
||||
is_first := encoder.encode_struct_fields[T](val, true, [], '')
|
||||
if encoder.prettify && !is_first {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
encoder.output << `}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_string(val string) {
|
||||
encoder.output << `"`
|
||||
mut buffer_start := 0
|
||||
mut buffer_end := 0
|
||||
for buffer_end < val.len {
|
||||
character := val[buffer_end]
|
||||
match character {
|
||||
`"`, `\\` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << character
|
||||
}
|
||||
`\b` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `b`
|
||||
}
|
||||
`\n` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `n`
|
||||
}
|
||||
`\f` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `f`
|
||||
}
|
||||
`\t` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `t`
|
||||
}
|
||||
`\r` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `r`
|
||||
}
|
||||
else {
|
||||
if character < 0x20 { // control characters
|
||||
unsafe {
|
||||
encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start)
|
||||
}
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `u`
|
||||
|
||||
hex_string := '${character:04x}'
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 4) }
|
||||
|
||||
continue
|
||||
}
|
||||
if encoder.escape_unicode {
|
||||
if character >= 0b1111_0000 { // four bytes
|
||||
unsafe {
|
||||
encoder.output.push_many(val.str + buffer_start,
|
||||
buffer_end - buffer_start)
|
||||
}
|
||||
unicode_point_low := val[buffer_end..buffer_end + 4].bytes().byterune() or {
|
||||
0
|
||||
} - 0x10000
|
||||
|
||||
hex_string := '\\u${0xD800 + ((unicode_point_low >> 10) & 0x3FF):04X}\\u${
|
||||
0xDC00 + (unicode_point_low & 0x3FF):04x}'
|
||||
|
||||
buffer_end += 4
|
||||
buffer_start = buffer_end
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 12) }
|
||||
|
||||
continue
|
||||
} else if character >= 0b1110_0000 { // three bytes
|
||||
unsafe {
|
||||
encoder.output.push_many(val.str + buffer_start,
|
||||
buffer_end - buffer_start)
|
||||
}
|
||||
hex_string := '\\u${val[buffer_end..buffer_end + 3].bytes().byterune() or {
|
||||
0
|
||||
}:04x}'
|
||||
|
||||
buffer_end += 3
|
||||
buffer_start = buffer_end
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 6) }
|
||||
|
||||
continue
|
||||
} else if character >= 0b1100_0000 { // two bytes
|
||||
unsafe {
|
||||
encoder.output.push_many(val.str + buffer_start,
|
||||
buffer_end - buffer_start)
|
||||
}
|
||||
hex_string := '\\u${val[buffer_end..buffer_end + 2].bytes().byterune() or {
|
||||
0
|
||||
}:04x}'
|
||||
|
||||
buffer_end += 2
|
||||
buffer_start = buffer_end
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 6) }
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
buffer_end++
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
|
||||
encoder.output << `"`
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_boolean(val bool) {
|
||||
if val {
|
||||
unsafe { encoder.output.push_many(true_string.str, true_string.len) }
|
||||
} else {
|
||||
unsafe { encoder.output.push_many(false_string.str, false_string.len) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_number[T](val T) {
|
||||
mut integer_val := ''
|
||||
$if T is u8 {
|
||||
integer_val = u8(val).str()
|
||||
} $else $if T is u16 {
|
||||
integer_val = u16(val).str()
|
||||
} $else $if T is u32 {
|
||||
integer_val = u32(val).str()
|
||||
} $else $if T is u64 {
|
||||
integer_val = u64(val).str()
|
||||
} $else $if T is i8 {
|
||||
integer_val = i8(val).str()
|
||||
} $else $if T is i16 {
|
||||
integer_val = i16(val).str()
|
||||
} $else $if T is int || T is i32 {
|
||||
integer_val = i32(val).str()
|
||||
} $else $if T is i64 {
|
||||
integer_val = i64(val).str()
|
||||
} $else $if T is usize {
|
||||
integer_val = usize(val).str()
|
||||
} $else $if T is isize {
|
||||
integer_val = isize(val).str()
|
||||
} $else $if T is f32 {
|
||||
integer_val = f32(val).str()
|
||||
} $else $if T is f64 {
|
||||
integer_val = f64(val).str()
|
||||
}
|
||||
$if T is $float {
|
||||
if integer_val.len > 2 && integer_val[integer_val.len - 2] == `.`
|
||||
&& integer_val[integer_val.len - 1] == `0` { // ends in .0
|
||||
// `2.0` = > `2`
|
||||
// but skip float in scientific notation, `1e+10`
|
||||
unsafe {
|
||||
integer_val.len -= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut encoder Encoder) encode_null() {
|
||||
unsafe { encoder.output.push_many(null_string.str, null_string.len) }
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_array[T](val T) {
|
||||
encoder.output << `[`
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
|
||||
for i, item in val {
|
||||
$if T is $pointer {
|
||||
if voidptr(item) == unsafe { nil } {
|
||||
encoder.encode_null()
|
||||
} else {
|
||||
unsafe { encoder.encode_pointer_array_item(item) }
|
||||
}
|
||||
} $else {
|
||||
encoder.encode_value(item)
|
||||
}
|
||||
if i < val.len - 1 {
|
||||
encoder.output << `,`
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
} else {
|
||||
if encoder.prettify {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoder.output << `]`
|
||||
}
|
||||
|
||||
@[unsafe]
|
||||
fn (mut encoder Encoder) encode_pointer_array_item[T](item T) {
|
||||
encoder.encode_value(*item)
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_map[K, T](val map[K]T) {
|
||||
encoder.output << `{`
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
|
||||
mut i := 0
|
||||
for key, value in val {
|
||||
encoder.encode_string('${key}')
|
||||
encoder.output << `:`
|
||||
if encoder.prettify {
|
||||
encoder.output << ` `
|
||||
}
|
||||
encoder.encode_value[T](value)
|
||||
if i < val.len - 1 {
|
||||
encoder.output << `,`
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
} else {
|
||||
if encoder.prettify {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
encoder.output << `}`
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_enum[T](val T) {
|
||||
if encoder.enum_as_int || enum_uses_json_as_number[T]() {
|
||||
encoder.encode_number(int(val))
|
||||
} else {
|
||||
mut enum_val := 'unknown enum value'
|
||||
$for member in T.values {
|
||||
if member.value == val {
|
||||
enum_val = member.name
|
||||
for attr in member.attrs {
|
||||
if json_attr := json_attr_value(attr) {
|
||||
enum_val = json_attr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
encoder.output << `"`
|
||||
unsafe { encoder.output.push_many(enum_val.str, enum_val.len) }
|
||||
encoder.output << `"`
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_sumtype[T](val T) {
|
||||
$if T is $pointer {
|
||||
// Pointer types are handled by encode_value's $pointer branch;
|
||||
// this instantiation is generated but never called.
|
||||
} $else {
|
||||
$for variant in T.variants {
|
||||
if val is variant {
|
||||
variant_name := sumtype_variant_name(typeof(variant.typ).name)
|
||||
$if variant.typ is time.Time {
|
||||
if T.name in ['x.json2.Any', 'json2.Any', 'Any'] {
|
||||
variant_value := val
|
||||
encoder.encode_value(variant_value)
|
||||
} else {
|
||||
encoder.encode_sumtype_time_variant(val, variant_name)
|
||||
}
|
||||
} $else $if variant.typ is $struct {
|
||||
if T.name in ['x.json2.Any', 'json2.Any', 'Any'] {
|
||||
variant_value := val
|
||||
encoder.encode_value(variant_value)
|
||||
} else {
|
||||
encoder.encode_sumtype_struct_variant(val, variant_name)
|
||||
}
|
||||
} $else $if variant.typ is $map {
|
||||
encoder.encode_value(val)
|
||||
} $else $if variant.typ is $array_dynamic {
|
||||
if T.name in ['x.json2.Any', 'json2.Any', 'Any'] {
|
||||
variant_value := val
|
||||
encoder.encode_value(variant_value)
|
||||
} else {
|
||||
variant_value := val
|
||||
encoder.encode_array_of_sumtype_variants(variant_value)
|
||||
}
|
||||
} $else {
|
||||
variant_value := val
|
||||
encoder.encode_value(variant_value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_object_key(is_first bool, key string) bool {
|
||||
if is_first {
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
}
|
||||
} else {
|
||||
encoder.output << `,`
|
||||
}
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
encoder.encode_string(key)
|
||||
encoder.output << `:`
|
||||
if encoder.prettify {
|
||||
encoder.output << ` `
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_sumtype_struct_variant[T](val T, variant_name string) {
|
||||
$for field in T.fields {
|
||||
$if field.is_embed {
|
||||
unsafe { encoder.encode_sumtype_struct_variant_with_embeds(val, variant_name) }
|
||||
return
|
||||
}
|
||||
}
|
||||
encoder.output << `{`
|
||||
mut is_first := unsafe { encoder.encode_struct_fields[T](val, true, [], '') }
|
||||
is_first = encoder.encode_object_key(is_first, '_type')
|
||||
encoder.encode_string(variant_name)
|
||||
if encoder.prettify && !is_first {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
encoder.output << `}`
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_array_of_sumtype_variants[T](val []T) {
|
||||
encoder.output << `[`
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
for i, item in val {
|
||||
$if T is time.Time {
|
||||
encoder.encode_value(item)
|
||||
} $else $if T is JsonEncoder {
|
||||
encoder.encode_value(item)
|
||||
} $else $if T is Encodable {
|
||||
encoder.encode_value(item)
|
||||
} $else $if T is $struct {
|
||||
elem_name := sumtype_variant_name(T.name)
|
||||
encoder.encode_sumtype_struct_variant(item, elem_name)
|
||||
} $else {
|
||||
encoder.encode_value(item)
|
||||
}
|
||||
if i < val.len - 1 {
|
||||
encoder.output << `,`
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
} else {
|
||||
if encoder.prettify {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
}
|
||||
}
|
||||
encoder.output << `]`
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn (mut encoder Encoder) encode_sumtype_time_variant(val time.Time, variant_name string) {
|
||||
encoder.output << `{`
|
||||
mut is_first := true
|
||||
is_first = encoder.encode_object_key(is_first, '_type')
|
||||
encoder.encode_string(variant_name)
|
||||
is_first = encoder.encode_object_key(is_first, 'value')
|
||||
encoder.encode_number(val.unix())
|
||||
if encoder.prettify && !is_first {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
encoder.output << `}`
|
||||
}
|
||||
|
||||
struct EncoderFieldInfo {
|
||||
key_name string
|
||||
|
||||
is_skip bool
|
||||
is_omitempty bool
|
||||
is_required bool
|
||||
is_json_null bool
|
||||
}
|
||||
|
||||
fn get_value_from_optional[T](val ?T) T {
|
||||
return val or { T{} }
|
||||
}
|
||||
|
||||
fn check_not_empty[T](val T) ?bool {
|
||||
$if val is ?string {
|
||||
opt := ?string(val)
|
||||
if sval := opt {
|
||||
return sval != ''
|
||||
}
|
||||
return false
|
||||
} $else $if val is ?int {
|
||||
opt := ?int(val)
|
||||
if ival := opt {
|
||||
return ival != 0
|
||||
}
|
||||
return false
|
||||
} $else $if val is ?f64 {
|
||||
opt := ?f64(val)
|
||||
if fval := opt {
|
||||
return fval != 0.0
|
||||
}
|
||||
return false
|
||||
} $else $if val is ?f32 {
|
||||
opt := ?f32(val)
|
||||
if fval := opt {
|
||||
return fval != 0.0
|
||||
}
|
||||
return false
|
||||
} $else $if T is $option {
|
||||
return !struct_field_is_none(val)
|
||||
} $else $if T.indirections != 0 {
|
||||
return val != unsafe { nil }
|
||||
} $else $if T.unaliased_typ is string {
|
||||
if val == '' {
|
||||
return false
|
||||
}
|
||||
} $else $if T.unaliased_typ is $int || T.unaliased_typ is $float {
|
||||
if val == 0 {
|
||||
return false
|
||||
}
|
||||
} $else $if T.unaliased_typ is $array || T.unaliased_typ is $map {
|
||||
return val.len != 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: fix compilation with -autofree, and remove the tag @[manualfree] here:
|
||||
@[manualfree; unsafe]
|
||||
fn (mut encoder Encoder) cached_field_infos[T]() []EncoderFieldInfo {
|
||||
static field_infos := &[]EncoderFieldInfo(nil)
|
||||
if field_infos == nil {
|
||||
field_infos = &[]EncoderFieldInfo{}
|
||||
$for field in T.fields {
|
||||
mut is_skip := false
|
||||
mut key_name := ''
|
||||
mut is_omitempty := false
|
||||
mut is_required := false
|
||||
mut is_json_null := false
|
||||
for attr in field.attrs {
|
||||
match attr {
|
||||
'skip' {
|
||||
is_skip = true
|
||||
break
|
||||
}
|
||||
'omitempty' {
|
||||
is_omitempty = true
|
||||
}
|
||||
'required' {
|
||||
is_required = true
|
||||
}
|
||||
'json_null' {
|
||||
is_json_null = true
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
if attr.starts_with('json:') {
|
||||
json_attr := json_attr_value(attr) or { continue }
|
||||
if json_attr == '-' {
|
||||
is_skip = true
|
||||
break
|
||||
}
|
||||
key_name = json_attr
|
||||
}
|
||||
}
|
||||
field_infos << EncoderFieldInfo{
|
||||
key_name: if key_name == '' { field.name } else { key_name }
|
||||
is_skip: is_skip
|
||||
is_omitempty: is_omitempty
|
||||
is_required: is_required
|
||||
is_json_null: is_json_null
|
||||
}
|
||||
}
|
||||
}
|
||||
return *field_infos
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_struct_field_value[T](val T) {
|
||||
$if T is $interface {
|
||||
encoder.encode_null()
|
||||
} $else $if T.unaliased_typ is voidptr {
|
||||
encoder.encode_null()
|
||||
} $else $if T.pointee_type is $interface {
|
||||
encoder.encode_null()
|
||||
} $else $if T is $option {
|
||||
if val == none {
|
||||
unsafe { encoder.output.push_many(null_string.str, null_string.len) }
|
||||
} else {
|
||||
encoder.encode_value(get_value_from_optional(val))
|
||||
}
|
||||
} $else $if T.indirections == 1 {
|
||||
encoder.encode_value(*val)
|
||||
} $else $if T.indirections == 2 {
|
||||
encoder.encode_value(**val)
|
||||
} $else $if T.indirections == 3 {
|
||||
encoder.encode_value(***val)
|
||||
} $else {
|
||||
encoder.encode_value(val)
|
||||
}
|
||||
}
|
||||
|
||||
fn struct_field_is_none[T](val T) bool {
|
||||
$if T is $option {
|
||||
return val == none
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn struct_field_is_nil[T](val T) bool {
|
||||
$if T.indirections != 0 {
|
||||
return val == unsafe { nil }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn struct_field_should_encode[T](field_info EncoderFieldInfo, val T) bool {
|
||||
if field_info.is_skip {
|
||||
return false
|
||||
}
|
||||
if field_info.is_omitempty {
|
||||
if !(check_not_empty(val) or { false }) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !field_info.is_required && !field_info.is_json_null && struct_field_is_none(val) {
|
||||
return false
|
||||
}
|
||||
if struct_field_is_nil(val) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@[unsafe]
|
||||
fn (mut encoder Encoder) encode_struct_with_embeds[T](val T) {
|
||||
encoder.output << `{`
|
||||
is_first := encoder.encode_embedded_struct_fields[T](val, true, [], [], '')
|
||||
if encoder.prettify && !is_first {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
encoder.output << `}`
|
||||
}
|
||||
|
||||
@[unsafe]
|
||||
fn (mut encoder Encoder) encode_sumtype_struct_variant_with_embeds[T](val T, variant_name string) {
|
||||
encoder.output << `{`
|
||||
mut is_first := encoder.encode_embedded_struct_fields[T](val, true, [], [], '')
|
||||
is_first = encoder.encode_object_key(is_first, '_type')
|
||||
encoder.encode_string(variant_name)
|
||||
if encoder.prettify && !is_first {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
encoder.output << `}`
|
||||
}
|
||||
|
||||
@[unsafe]
|
||||
fn (mut encoder Encoder) encode_struct_fields[T](val T, was_first bool, old_used_keys []string, prefix string) bool {
|
||||
field_infos := encoder.cached_field_infos[T]()
|
||||
mut is_first := was_first
|
||||
mut used_keys := old_used_keys
|
||||
mut i := 0
|
||||
|
||||
$for field in T.fields {
|
||||
$if !field.is_embed {
|
||||
field_info := field_infos[i]
|
||||
mut write_field := true
|
||||
|
||||
$if field.typ is $shared {
|
||||
shared field_value := unsafe { val.$(field.name) }
|
||||
rlock field_value {
|
||||
write_field = struct_field_should_encode(field_info, field_value)
|
||||
|
||||
if write_field {
|
||||
if field_info.key_name in old_used_keys {
|
||||
is_first = encoder.encode_object_key(is_first, prefix +
|
||||
field_info.key_name)
|
||||
} else {
|
||||
is_first = encoder.encode_object_key(is_first, field_info.key_name)
|
||||
used_keys << field_info.key_name
|
||||
}
|
||||
encoder.encode_struct_field_value(field_value)
|
||||
}
|
||||
}
|
||||
} $else {
|
||||
write_field = struct_field_should_encode(field_info, val.$(field.name))
|
||||
|
||||
if write_field {
|
||||
if field_info.key_name in old_used_keys {
|
||||
is_first = encoder.encode_object_key(is_first, prefix + field_info.key_name)
|
||||
} else {
|
||||
is_first = encoder.encode_object_key(is_first, field_info.key_name)
|
||||
used_keys << field_info.key_name
|
||||
}
|
||||
encoder.encode_struct_field_value(val.$(field.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
$for field in T.fields {
|
||||
$if field.is_embed {
|
||||
new_prefix := prefix + field.name + '.'
|
||||
$if field.typ is $shared {
|
||||
shared field_value := unsafe { val.$(field.name) }
|
||||
rlock field_value {
|
||||
is_first = encoder.encode_struct_fields(field_value, is_first, used_keys,
|
||||
new_prefix)
|
||||
}
|
||||
} $else {
|
||||
is_first = encoder.encode_struct_fields(val.$(field.name), is_first, used_keys,
|
||||
new_prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
return is_first
|
||||
}
|
||||
|
||||
@[unsafe]
|
||||
fn (mut encoder Encoder) encode_embedded_struct_fields[T](val T, was_first bool, old_used_keys []string, reserved_keys []string, prefix string) bool {
|
||||
field_infos := encoder.cached_field_infos[T]()
|
||||
mut is_first := was_first
|
||||
mut used_keys := old_used_keys.clone()
|
||||
mut i := 0
|
||||
|
||||
$for field in T.fields {
|
||||
$if field.is_embed {
|
||||
mut child_reserved_keys := reserved_keys.clone()
|
||||
mut reserved_i := 0
|
||||
$for reserved_field in T.fields {
|
||||
reserved_field_info := field_infos[reserved_i]
|
||||
$if !reserved_field.is_embed {
|
||||
if !reserved_field_info.is_skip {
|
||||
child_reserved_keys << reserved_field_info.key_name
|
||||
}
|
||||
}
|
||||
reserved_i++
|
||||
}
|
||||
new_prefix := prefix + field.name + '.'
|
||||
$if field.typ is $shared {
|
||||
shared field_value := unsafe { val.$(field.name) }
|
||||
rlock field_value {
|
||||
is_first = encoder.encode_embedded_struct_fields(field_value, is_first,
|
||||
used_keys, child_reserved_keys, new_prefix)
|
||||
}
|
||||
} $else {
|
||||
is_first = encoder.encode_embedded_struct_fields(val.$(field.name), is_first,
|
||||
used_keys, child_reserved_keys, new_prefix)
|
||||
}
|
||||
} $else {
|
||||
field_info := field_infos[i]
|
||||
mut write_field := true
|
||||
$if field.typ is $shared {
|
||||
shared field_value := unsafe { val.$(field.name) }
|
||||
rlock field_value {
|
||||
write_field = struct_field_should_encode(field_info, field_value)
|
||||
if write_field {
|
||||
should_prefix := field_info.key_name in used_keys
|
||||
|| field_info.key_name in reserved_keys
|
||||
json_key := if should_prefix {
|
||||
prefix + field_info.key_name
|
||||
} else {
|
||||
field_info.key_name
|
||||
}
|
||||
is_first = encoder.encode_object_key(is_first, json_key)
|
||||
if !should_prefix {
|
||||
used_keys << field_info.key_name
|
||||
}
|
||||
encoder.encode_struct_field_value(field_value)
|
||||
}
|
||||
}
|
||||
} $else {
|
||||
write_field = struct_field_should_encode(field_info, val.$(field.name))
|
||||
if write_field {
|
||||
should_prefix := field_info.key_name in used_keys
|
||||
|| field_info.key_name in reserved_keys
|
||||
json_key := if should_prefix {
|
||||
prefix + field_info.key_name
|
||||
} else {
|
||||
field_info.key_name
|
||||
}
|
||||
is_first = encoder.encode_object_key(is_first, json_key)
|
||||
if !should_prefix {
|
||||
used_keys << field_info.key_name
|
||||
}
|
||||
encoder.encode_struct_field_value(val.$(field.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
return is_first
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_custom[T](val T) {
|
||||
integer_val := val.to_json()
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_custom2[T](val T) {
|
||||
integer_val := val.json_str()
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) increment_level() {
|
||||
encoder.level++
|
||||
encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level)
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) decrement_level() {
|
||||
encoder.level--
|
||||
encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level)
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) add_indent() {
|
||||
unsafe { encoder.output.push_many(encoder.prefix.str, encoder.prefix.len) }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module json2
|
||||
|
||||
// encode_pretty ...
|
||||
@[deprecated: 'use `encode(..., prettify: true)` instead']
|
||||
@[deprecated_after: '2025-10-30']
|
||||
pub fn encode_pretty[T](typed_data T) string {
|
||||
return encode(typed_data, prettify: true)
|
||||
}
|
||||
|
||||
// str returns the JSON string representation of the `map[string]Any` type.
|
||||
pub fn (f map[string]Any) str() string {
|
||||
return Any(f).json_str()
|
||||
}
|
||||
|
||||
// str returns the JSON string representation of the `[]Any` type.
|
||||
pub fn (f []Any) str() string {
|
||||
return Any(f).json_str()
|
||||
}
|
||||
|
||||
// str returns the string representation of the `Any` type. Use the `json_str` method.
|
||||
// If you want to use the escaped str() version of the `Any` type.
|
||||
pub fn (f Any) str() string {
|
||||
if f is string {
|
||||
return f
|
||||
} else {
|
||||
return f.json_str()
|
||||
}
|
||||
}
|
||||
|
||||
// json_str returns the JSON string representation of the `Any` type.
|
||||
pub fn (f Any) json_str() string {
|
||||
return encode(f)
|
||||
}
|
||||
|
||||
// prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type.
|
||||
@[deprecated: 'use `encode(Any(...), prettify: true)` instead']
|
||||
@[deprecated_after: '2025-10-30']
|
||||
pub fn (f Any) prettify_json_str() string {
|
||||
return encode(f, prettify: true)
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module json2
|
||||
|
||||
import time
|
||||
|
||||
// i8 uses `Any` as a 16-bit integer.
|
||||
pub fn (f Any) i8() i8 {
|
||||
match f {
|
||||
i8 {
|
||||
return f
|
||||
}
|
||||
i16, i32, int, i64, u8, u16, u32, u64, f32, f64, bool {
|
||||
return i8(f)
|
||||
}
|
||||
string {
|
||||
return f.i8()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// i16 uses `Any` as a 16-bit integer.
|
||||
pub fn (f Any) i16() i16 {
|
||||
match f {
|
||||
i16 {
|
||||
return f
|
||||
}
|
||||
i8, i32, int, i64, u8, u16, u32, u64, f32, f64, bool {
|
||||
return i16(f)
|
||||
}
|
||||
string {
|
||||
return f.i16()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// int uses `Any` as an integer.
|
||||
pub fn (f Any) int() int {
|
||||
match f {
|
||||
int {
|
||||
return f
|
||||
}
|
||||
i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, bool {
|
||||
return int(f)
|
||||
}
|
||||
string {
|
||||
return f.int()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// i32 uses `Any` as a 32-bit integer.
|
||||
pub fn (f Any) i32() i32 {
|
||||
match f {
|
||||
i32 {
|
||||
return f
|
||||
}
|
||||
i8, i16, int, i64, u8, u16, u32, u64, f32, f64, bool {
|
||||
return i32(f)
|
||||
}
|
||||
string {
|
||||
return f.i32()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// i64 uses `Any` as a 64-bit integer.
|
||||
pub fn (f Any) i64() i64 {
|
||||
match f {
|
||||
i64 {
|
||||
return f
|
||||
}
|
||||
i8, i16, i32, int, u8, u16, u32, u64, f32, f64, bool {
|
||||
return i64(f)
|
||||
}
|
||||
string {
|
||||
return f.i64()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// u8 uses `Any` as a 8-bit unsigned integer.
|
||||
pub fn (f Any) u8() u8 {
|
||||
match f {
|
||||
u8 {
|
||||
return f
|
||||
}
|
||||
u16, u32, u64, i8, i16, i32, int, i64, f32, f64, bool {
|
||||
return u8(u16(f))
|
||||
}
|
||||
string {
|
||||
return f.u8()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// u16 uses `Any` as a 16-bit unsigned integer.
|
||||
pub fn (f Any) u16() u16 {
|
||||
match f {
|
||||
u16 {
|
||||
return f
|
||||
}
|
||||
u8, u32, u64, i8, i16, i32, int, i64, f32, f64, bool {
|
||||
return u16(f)
|
||||
}
|
||||
string {
|
||||
return f.u16()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// u32 uses `Any` as a 32-bit unsigned integer.
|
||||
pub fn (f Any) u32() u32 {
|
||||
match f {
|
||||
u32 {
|
||||
return f
|
||||
}
|
||||
u8, u16, u64, i8, i16, i32, int, i64, f32, f64, bool {
|
||||
return u32(f)
|
||||
}
|
||||
string {
|
||||
return f.u32()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// u64 uses `Any` as a 64-bit unsigned integer.
|
||||
pub fn (f Any) u64() u64 {
|
||||
match f {
|
||||
u64 {
|
||||
return f
|
||||
}
|
||||
u8, u16, u32, i8, i16, i32, int, i64, f32, f64, bool {
|
||||
return u64(f)
|
||||
}
|
||||
string {
|
||||
return f.u64()
|
||||
}
|
||||
else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// f32 uses `Any` as a 32-bit float.
|
||||
pub fn (f Any) f32() f32 {
|
||||
match f {
|
||||
f32 {
|
||||
return f
|
||||
}
|
||||
bool, i8, i16, i32, int, i64, u8, u16, u32, u64, f64 {
|
||||
return f32(f)
|
||||
}
|
||||
string {
|
||||
return f.f32()
|
||||
}
|
||||
else {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// f64 uses `Any` as a 64-bit float.
|
||||
pub fn (f Any) f64() f64 {
|
||||
match f {
|
||||
f64 {
|
||||
return f
|
||||
}
|
||||
i8, i16, i32, int, i64, u8, u16, u32, u64, f32 {
|
||||
return f64(f)
|
||||
}
|
||||
string {
|
||||
return f.f64()
|
||||
}
|
||||
else {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bool uses `Any` as a bool.
|
||||
pub fn (f Any) bool() bool {
|
||||
match f {
|
||||
bool {
|
||||
return f
|
||||
}
|
||||
string {
|
||||
if f == 'false' {
|
||||
return false
|
||||
}
|
||||
if f == 'true' {
|
||||
return true
|
||||
}
|
||||
if f.len > 0 {
|
||||
return f != '0' && f != '0.0'
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
i8, i16, i32, int, i64 {
|
||||
return i64(f) != 0
|
||||
}
|
||||
u8, u16, u32, u64 {
|
||||
return u64(f) != 0
|
||||
}
|
||||
f32, f64 {
|
||||
return f64(f) != 0.0
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// arr uses `Any` as an array.
|
||||
@[deprecated: 'use as_array() instead']
|
||||
@[deprecated_after: '2026-03-27']
|
||||
pub fn (f Any) arr() []Any {
|
||||
return f.as_array()
|
||||
}
|
||||
|
||||
// as_array uses `Any` as an array.
|
||||
pub fn (f Any) as_array() []Any {
|
||||
if f is []Any {
|
||||
return f
|
||||
} else if f is map[string]Any {
|
||||
mut arr := []Any{}
|
||||
for _, v in f {
|
||||
arr << v
|
||||
}
|
||||
return arr
|
||||
}
|
||||
return [f]
|
||||
}
|
||||
|
||||
// as_map uses `Any` as a map.
|
||||
pub fn (f Any) as_map() map[string]Any {
|
||||
if f is map[string]Any {
|
||||
return f
|
||||
} else if f is []Any {
|
||||
mut mp := map[string]Any{}
|
||||
for i, fi in f {
|
||||
mp['${i}'] = fi
|
||||
}
|
||||
return mp
|
||||
}
|
||||
return {
|
||||
'0': f
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (f Any) as_map_of_strings() map[string]string {
|
||||
if f is map[string]Any {
|
||||
mut ms := map[string]string{}
|
||||
for k, v in f {
|
||||
ms[k] = v.str()
|
||||
}
|
||||
return ms
|
||||
}
|
||||
if f is []Any {
|
||||
mut ms := map[string]string{}
|
||||
for i, fi in f {
|
||||
ms['${i}'] = fi.str()
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return {
|
||||
'0': f.str()
|
||||
}
|
||||
}
|
||||
|
||||
// to_time uses `Any` as a time.Time.
|
||||
pub fn (f Any) to_time() !time.Time {
|
||||
match f {
|
||||
time.Time {
|
||||
return f
|
||||
}
|
||||
i64 {
|
||||
return time.unix(f)
|
||||
}
|
||||
string {
|
||||
is_iso8601 := f[4] == `-` && f[7] == `-`
|
||||
if is_iso8601 {
|
||||
return time.parse_iso8601(f)!
|
||||
}
|
||||
is_rfc3339 := f.len == 24 && f[23] == `Z` && f[10] == `T`
|
||||
if is_rfc3339 {
|
||||
return time.parse_rfc3339(f)!
|
||||
}
|
||||
mut is_unix_timestamp := true
|
||||
for c in f {
|
||||
if c == `-` || (c >= `0` && c <= `9`) {
|
||||
continue
|
||||
}
|
||||
is_unix_timestamp = false
|
||||
break
|
||||
}
|
||||
if is_unix_timestamp {
|
||||
return time.unix(f.i64())
|
||||
}
|
||||
// TODO: parse_iso8601
|
||||
// TODO: parse_rfc2822
|
||||
return time.parse(f)!
|
||||
}
|
||||
else {
|
||||
return error('not a time value: ${f} of type: ${f.type_name()}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// map_from converts a struct to a map of Any.
|
||||
pub fn map_from[T](t T) map[string]Any {
|
||||
mut m := map[string]Any{}
|
||||
$if T is $struct {
|
||||
$for field in T.fields {
|
||||
value := t.$(field.name)
|
||||
|
||||
$if field.is_array {
|
||||
mut top_arr := unsafe { []Any{len: t.$(field.name).len} }
|
||||
for idx, variable in value {
|
||||
$if variable is $array {
|
||||
top_arr[idx] = flatten_array(variable)
|
||||
} $else {
|
||||
top_arr[idx] = variable
|
||||
}
|
||||
}
|
||||
m[field.name] = top_arr
|
||||
top_arr.clear()
|
||||
} $else $if field.is_struct {
|
||||
m[field.name] = map_from(value)
|
||||
} $else $if field.is_map {
|
||||
// TODO
|
||||
} $else $if field.is_alias {
|
||||
// TODO
|
||||
} $else $if field.is_option {
|
||||
// TODO
|
||||
} $else {
|
||||
// TODO: simplify check of type & improve memory usage when convert
|
||||
$if field.typ is string {
|
||||
m[field.name] = value
|
||||
} $else $if field.typ is bool {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is i8 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is i16 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is i32 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is int {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is i64 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is f32 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is f64 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is u8 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is u16 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is u32 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else $if field.typ is u64 {
|
||||
m[field.name] = t.$(field.name)
|
||||
} $else {
|
||||
// return error("The type of `${field.name}` can't be decoded. Please open an issue at https://github.com/vlang/v/issues/new/choose")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// flatten_array convert a array of values to array of Any
|
||||
@[inline]
|
||||
fn flatten_array[T](t []T) []Any {
|
||||
$if t !is $array {
|
||||
return Any(t)
|
||||
} $else {
|
||||
mut arr := []Any{}
|
||||
for variable in t {
|
||||
$if variable is $array {
|
||||
arr << flatten_array(variable)
|
||||
} $else {
|
||||
arr << Any(variable)
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,690 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module json2
|
||||
|
||||
import io
|
||||
import strconv
|
||||
|
||||
// JsonScanError describes a tokenization error reported by the iterative scanner APIs.
|
||||
pub struct JsonScanError {
|
||||
Error
|
||||
pub:
|
||||
message string
|
||||
|
||||
line int
|
||||
character int
|
||||
}
|
||||
|
||||
fn (e JsonScanError) msg() string {
|
||||
return '${e.line}:${e.character}: Invalid json token: ${e.message}'
|
||||
}
|
||||
|
||||
// Scanner tokenizes JSON from an in-memory string or byte slice.
|
||||
pub struct Scanner {
|
||||
mut:
|
||||
text []u8
|
||||
pos int // the position of the token in scanner text
|
||||
line int = 1
|
||||
col int = 1
|
||||
}
|
||||
|
||||
// ReaderScanner tokenizes JSON incrementally from any io.Reader.
|
||||
pub struct ReaderScanner {
|
||||
mut:
|
||||
reader &io.BufferedReader
|
||||
peeked bool
|
||||
ch u8
|
||||
line int = 1
|
||||
col int = 1
|
||||
}
|
||||
|
||||
// ReaderScannerConfig configures a reader-backed JSON scanner.
|
||||
@[params]
|
||||
pub struct ReaderScannerConfig {
|
||||
pub:
|
||||
reader io.Reader
|
||||
buffer_size int = 128 * 1024
|
||||
}
|
||||
|
||||
// TokenKind identifies the kind of a JSON token.
|
||||
pub enum TokenKind {
|
||||
none
|
||||
error
|
||||
str
|
||||
float
|
||||
int
|
||||
null
|
||||
bool
|
||||
eof
|
||||
comma = 44 // ,
|
||||
colon = 58 // :
|
||||
lsbr = 91 // [
|
||||
rsbr = 93 // ]
|
||||
lcbr = 123 // {
|
||||
rcbr = 125 // }
|
||||
}
|
||||
|
||||
// new_scanner creates an iterative scanner for an in-memory JSON string.
|
||||
pub fn new_scanner(text string) Scanner {
|
||||
return Scanner{
|
||||
text: text.bytes()
|
||||
line: 1
|
||||
col: 1
|
||||
}
|
||||
}
|
||||
|
||||
// new_scanner_from_bytes creates an iterative scanner for an in-memory JSON byte slice.
|
||||
pub fn new_scanner_from_bytes(text []u8) Scanner {
|
||||
return Scanner{
|
||||
text: text
|
||||
line: 1
|
||||
col: 1
|
||||
}
|
||||
}
|
||||
|
||||
// new_reader_scanner creates an iterative scanner that reads JSON tokens from an io.Reader.
|
||||
pub fn new_reader_scanner(config ReaderScannerConfig) &ReaderScanner {
|
||||
return &ReaderScanner{
|
||||
reader: io.new_buffered_reader(reader: config.reader, cap: config.buffer_size)
|
||||
line: 1
|
||||
col: 1
|
||||
}
|
||||
}
|
||||
|
||||
// free releases the reader scanner's internal buffer.
|
||||
pub fn (mut s ReaderScanner) free() {
|
||||
s.reader.free()
|
||||
}
|
||||
|
||||
pub struct Token {
|
||||
pub:
|
||||
lit []u8 // literal representation of the token
|
||||
kind TokenKind // the token number/enum; for quick comparisons
|
||||
line int // the line in the source where the token occurred
|
||||
col int // the column in the source where the token occurred
|
||||
}
|
||||
|
||||
// literal returns the token contents as a string.
|
||||
pub fn (t Token) literal() string {
|
||||
return t.lit.bytestr()
|
||||
}
|
||||
|
||||
// full_col returns the full column information which includes the length.
|
||||
pub fn (t Token) full_col() int {
|
||||
return t.col + t.lit.len
|
||||
}
|
||||
|
||||
// is_eof reports whether the token marks the end of the JSON stream.
|
||||
pub fn (t Token) is_eof() bool {
|
||||
return t.kind == .eof
|
||||
}
|
||||
|
||||
// list of characters commonly used in JSON.
|
||||
const char_list = [`{`, `}`, `[`, `]`, `,`, `:`]!
|
||||
// list of newlines to check when moving to a new position.
|
||||
const newlines = [`\r`, `\n`, `\t`]!
|
||||
// list of escapable that needs to be escaped inside a JSON string.
|
||||
// double quotes and forward slashes are excluded intentionally since
|
||||
// they have their own separate checks for it in order to pass the
|
||||
// JSON test suite (https://github.com/nst/JSONTestSuite/).
|
||||
const important_escapable_chars = [`\b`, `\f`, `\n`, `\r`, `\t`]!
|
||||
// list of valid unicode escapes aside from \u{4-hex digits}
|
||||
const valid_unicode_escapes = [`b`, `f`, `n`, `r`, `t`, `\\`, `"`, `/`]!
|
||||
// used for transforming escapes into valid unicode (eg. n => \n)
|
||||
const unicode_transform_escapes = {
|
||||
98: `\b`
|
||||
102: `\f`
|
||||
110: `\n`
|
||||
114: `\r`
|
||||
116: `\t`
|
||||
92: `\\`
|
||||
34: `"`
|
||||
47: `/`
|
||||
}
|
||||
const exp_signs = [u8(`-`), `+`]!
|
||||
|
||||
fn new_scan_error(message string, line int, col int) JsonScanError {
|
||||
return JsonScanError{
|
||||
message: message
|
||||
line: line
|
||||
character: col
|
||||
}
|
||||
}
|
||||
|
||||
fn token_to_scan_error(token Token) JsonScanError {
|
||||
return new_scan_error(token.literal(), token.line, token.col)
|
||||
}
|
||||
|
||||
fn important_escapable_char(ch u8) ?u8 {
|
||||
return match ch {
|
||||
`\b` { `b` }
|
||||
`\f` { `f` }
|
||||
`\n` { `n` }
|
||||
`\r` { `r` }
|
||||
`\t` { `t` }
|
||||
else { none }
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_token_description(ch u8) string {
|
||||
if ch >= 32 && ch <= 126 {
|
||||
x := ch.ascii_str()
|
||||
return 'invalid token `${x}`'
|
||||
} else {
|
||||
x := ch.str_escaped()
|
||||
return 'invalid token `${x}`'
|
||||
}
|
||||
}
|
||||
|
||||
// move_pos proceeds to the next position.
|
||||
fn (mut s Scanner) move() {
|
||||
s.move_pos(true, true)
|
||||
}
|
||||
|
||||
// move_pos_with_newlines is the same as move_pos but only enables newline checking.
|
||||
fn (mut s Scanner) move_pos_with_newlines() {
|
||||
s.move_pos(false, true)
|
||||
}
|
||||
|
||||
fn (mut s Scanner) move_pos(include_space bool, include_newlines bool) {
|
||||
s.pos++
|
||||
if s.pos < s.text.len {
|
||||
if include_newlines && s.text[s.pos] in newlines {
|
||||
s.line++
|
||||
s.col = 0
|
||||
if s.text[s.pos] == `\r` && s.pos + 1 < s.text.len && s.text[s.pos + 1] == `\n` {
|
||||
s.pos++
|
||||
}
|
||||
for s.pos < s.text.len && s.text[s.pos] in newlines {
|
||||
s.move()
|
||||
}
|
||||
} else if include_space && s.text[s.pos] == ` ` {
|
||||
s.pos++
|
||||
s.col++
|
||||
for s.pos < s.text.len && s.text[s.pos] == ` ` {
|
||||
s.move()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.col++
|
||||
}
|
||||
}
|
||||
|
||||
// error returns an error token.
|
||||
fn (s &Scanner) error(description string) Token {
|
||||
return s.tokenize(description.bytes(), .error)
|
||||
}
|
||||
|
||||
// tokenize returns a token based on the given lit and kind.
|
||||
fn (s &Scanner) tokenize(lit []u8, kind TokenKind) Token {
|
||||
return Token{
|
||||
lit: lit
|
||||
kind: kind
|
||||
col: s.col
|
||||
line: s.line
|
||||
}
|
||||
}
|
||||
|
||||
// text_scan scans and returns a string token.
|
||||
@[manualfree]
|
||||
fn (mut s Scanner) text_scan() Token {
|
||||
mut has_closed := false
|
||||
mut chrs := []u8{}
|
||||
for {
|
||||
s.pos++
|
||||
s.col++
|
||||
if s.pos >= s.text.len {
|
||||
break
|
||||
}
|
||||
ch := s.text[s.pos]
|
||||
if ch == `"` {
|
||||
has_closed = true
|
||||
break
|
||||
} else if escaped := important_escapable_char(ch) {
|
||||
return s.error('character must be escaped with a backslash, replace with: \\${escaped.ascii_str()}')
|
||||
} else if ch < 0x20 {
|
||||
return s.error('character must be escaped with a unicode escape, replace with: \\u${ch:04x}')
|
||||
} else if ch == `\\` {
|
||||
if s.pos == s.text.len - 1 {
|
||||
return s.error('incomplete backslash escape at end of JSON input')
|
||||
}
|
||||
|
||||
peek := s.text[s.pos + 1]
|
||||
if peek in valid_unicode_escapes {
|
||||
chrs << unicode_transform_escapes[int(peek)]
|
||||
s.pos++
|
||||
s.col++
|
||||
continue
|
||||
} else if peek == `u` {
|
||||
if s.pos + 5 < s.text.len {
|
||||
s.pos++
|
||||
s.col++
|
||||
mut codepoint := []u8{}
|
||||
codepoint_start := s.pos
|
||||
for s.pos < s.text.len && s.pos < codepoint_start + 4 {
|
||||
s.pos++
|
||||
s.col++
|
||||
if s.text[s.pos] == `"` {
|
||||
break
|
||||
} else if !s.text[s.pos].is_hex_digit() {
|
||||
x := s.text[s.pos].ascii_str()
|
||||
return s.error('`${x}` is not a hex digit')
|
||||
}
|
||||
codepoint << s.text[s.pos]
|
||||
}
|
||||
if codepoint.len != 4 {
|
||||
return s.error('unicode escape must have 4 hex digits')
|
||||
}
|
||||
val := u32(strconv.parse_uint(codepoint.bytestr(), 16, 32) or { 0 })
|
||||
converted := utf32_to_str(val)
|
||||
converted_bytes := converted.bytes()
|
||||
chrs << converted_bytes
|
||||
unsafe {
|
||||
converted.free()
|
||||
converted_bytes.free()
|
||||
codepoint.free()
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
return s.error('incomplete unicode escape')
|
||||
}
|
||||
} else if peek == `U` {
|
||||
return s.error('unicode endpoints must be in lowercase `u`')
|
||||
} else if peek == u8(229) {
|
||||
return s.error('unicode endpoint not allowed')
|
||||
} else {
|
||||
return s.error('invalid backslash escape')
|
||||
}
|
||||
}
|
||||
chrs << ch
|
||||
}
|
||||
tok := s.tokenize(chrs, .str)
|
||||
s.move()
|
||||
if !has_closed {
|
||||
return s.error('missing double quotes in string closing')
|
||||
}
|
||||
return tok
|
||||
}
|
||||
|
||||
// num_scan scans and returns an int/float token.
|
||||
fn (mut s Scanner) num_scan() Token {
|
||||
// analyze json number structure
|
||||
// -[digit][?[dot][digit]][?[E/e][?-/+][digit]]
|
||||
mut is_fl := false
|
||||
mut dot_index := -1
|
||||
mut digits := []u8{}
|
||||
if s.text[s.pos] == `-` {
|
||||
digits << `-`
|
||||
if s.pos + 1 >= s.text.len || !s.text[s.pos + 1].is_digit() {
|
||||
return s.invalid_token()
|
||||
}
|
||||
s.move_pos_with_newlines()
|
||||
}
|
||||
if s.text[s.pos] == `0` && (s.pos + 1 < s.text.len && s.text[s.pos + 1].is_digit()) {
|
||||
return s.error('leading zeroes in a number are not allowed')
|
||||
}
|
||||
for s.pos < s.text.len && (s.text[s.pos].is_digit() || (!is_fl && s.text[s.pos] == `.`)) {
|
||||
digits << s.text[s.pos]
|
||||
if s.text[s.pos] == `.` {
|
||||
is_fl = true
|
||||
dot_index = digits.len - 1
|
||||
}
|
||||
s.move_pos_with_newlines()
|
||||
}
|
||||
if dot_index + 1 < s.text.len && digits[dot_index + 1..].len == 0 {
|
||||
return s.error('invalid float')
|
||||
}
|
||||
if s.pos < s.text.len && (s.text[s.pos] == `e` || s.text[s.pos] == `E`) {
|
||||
digits << s.text[s.pos]
|
||||
s.move_pos_with_newlines()
|
||||
if s.pos < s.text.len && s.text[s.pos] in exp_signs {
|
||||
digits << s.text[s.pos]
|
||||
s.move_pos_with_newlines()
|
||||
}
|
||||
mut exp_digits_count := 0
|
||||
for s.pos < s.text.len && s.text[s.pos].is_digit() {
|
||||
digits << s.text[s.pos]
|
||||
exp_digits_count++
|
||||
s.move_pos_with_newlines()
|
||||
}
|
||||
if exp_digits_count == 0 {
|
||||
return s.error('invalid exponent')
|
||||
}
|
||||
}
|
||||
kind := if is_fl { TokenKind.float } else { TokenKind.int }
|
||||
return s.tokenize(digits, kind)
|
||||
}
|
||||
|
||||
// invalid_token returns an error token with the invalid token message.
|
||||
fn (s &Scanner) invalid_token() Token {
|
||||
return s.error(invalid_token_description(s.text[s.pos]))
|
||||
}
|
||||
|
||||
// next returns the next JSON token from the in-memory scanner.
|
||||
pub fn (mut s Scanner) next() !Token {
|
||||
tok := s.scan()
|
||||
if tok.kind == .error {
|
||||
return token_to_scan_error(tok)
|
||||
}
|
||||
return tok
|
||||
}
|
||||
|
||||
// scan returns a token based on the scanner's current position.
|
||||
// used to set the next token
|
||||
@[manualfree]
|
||||
fn (mut s Scanner) scan() Token {
|
||||
if s.pos < s.text.len && (s.text[s.pos] == ` ` || s.text[s.pos] in newlines) {
|
||||
s.move()
|
||||
}
|
||||
if s.pos >= s.text.len {
|
||||
return s.tokenize([]u8{}, .eof)
|
||||
} else if s.pos + 3 < s.text.len && (s.text[s.pos] == `t` || s.text[s.pos] == `n`) {
|
||||
ident := s.text[s.pos..s.pos + 4].bytestr()
|
||||
if ident == 'true' || ident == 'null' {
|
||||
mut kind := TokenKind.null
|
||||
if ident == 'true' {
|
||||
kind = .bool
|
||||
}
|
||||
unsafe { ident.free() }
|
||||
val := s.text[s.pos..s.pos + 4]
|
||||
tok := s.tokenize(val, kind)
|
||||
s.move() // n / t
|
||||
s.move() // u / r
|
||||
s.move() // l / u
|
||||
s.move() // l / e
|
||||
return tok
|
||||
}
|
||||
unsafe { ident.free() }
|
||||
return s.invalid_token()
|
||||
} else if s.pos + 4 < s.text.len && s.text[s.pos] == `f` {
|
||||
ident := s.text[s.pos..s.pos + 5].bytestr()
|
||||
if ident == 'false' {
|
||||
unsafe { ident.free() }
|
||||
val := s.text[s.pos..s.pos + 5]
|
||||
tok := s.tokenize(val, .bool)
|
||||
s.move() // f
|
||||
s.move() // a
|
||||
s.move() // l
|
||||
s.move() // s
|
||||
s.move() // e
|
||||
return tok
|
||||
}
|
||||
unsafe { ident.free() }
|
||||
return s.invalid_token()
|
||||
} else if s.text[s.pos] in char_list {
|
||||
chr := s.text[s.pos]
|
||||
tok := s.tokenize([]u8{}, unsafe { TokenKind(int(chr)) })
|
||||
s.move()
|
||||
return tok
|
||||
} else if s.text[s.pos] == `"` {
|
||||
return s.text_scan()
|
||||
} else if s.text[s.pos].is_digit() || s.text[s.pos] == `-` {
|
||||
return s.num_scan()
|
||||
} else {
|
||||
return s.invalid_token()
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) tokenize(lit []u8, kind TokenKind, line int, col int) Token {
|
||||
return Token{
|
||||
lit: lit
|
||||
kind: kind
|
||||
line: line
|
||||
col: col
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) has_next_byte() !bool {
|
||||
if s.peeked {
|
||||
return true
|
||||
}
|
||||
mut buf := [u8(0)]
|
||||
n := s.reader.read(mut buf) or {
|
||||
if err is io.Eof {
|
||||
return false
|
||||
}
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
s.ch = buf[0]
|
||||
s.peeked = true
|
||||
return true
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) peek_byte() !u8 {
|
||||
if !s.has_next_byte()! {
|
||||
return io.Eof{}
|
||||
}
|
||||
return s.ch
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) advance_position(ch u8) ! {
|
||||
if ch == `\r` {
|
||||
if s.has_next_byte()! && s.ch == `\n` {
|
||||
s.peeked = false
|
||||
}
|
||||
}
|
||||
if ch in newlines {
|
||||
s.line++
|
||||
s.col = 1
|
||||
return
|
||||
}
|
||||
s.col++
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) read_byte() !u8 {
|
||||
ch := s.peek_byte()!
|
||||
s.peeked = false
|
||||
s.advance_position(ch)!
|
||||
return ch
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) skip_whitespace() ! {
|
||||
for {
|
||||
if !s.has_next_byte()! {
|
||||
return
|
||||
}
|
||||
ch := s.ch
|
||||
if ch == ` ` || ch in newlines {
|
||||
_ = s.read_byte()!
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) scan_ident(ident string, kind TokenKind, line int, col int) !Token {
|
||||
mut lit := []u8{}
|
||||
for expected in ident.bytes() {
|
||||
current_line, current_col := s.line, s.col
|
||||
ch := s.read_byte() or {
|
||||
if err is io.Eof {
|
||||
return new_scan_error('unexpected end of JSON input', current_line, current_col)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if ch != expected {
|
||||
return new_scan_error(invalid_token_description(ch), current_line, current_col)
|
||||
}
|
||||
lit << ch
|
||||
}
|
||||
return s.tokenize(lit, kind, line, col)
|
||||
}
|
||||
|
||||
@[manualfree]
|
||||
fn (mut s ReaderScanner) text_scan(line int, col int) !Token {
|
||||
mut chrs := []u8{}
|
||||
_ = s.read_byte()! // opening quote
|
||||
for {
|
||||
current_line, current_col := s.line, s.col
|
||||
if !s.has_next_byte()! {
|
||||
return new_scan_error('missing double quotes in string closing', line, col)
|
||||
}
|
||||
ch := s.ch
|
||||
if ch == `"` {
|
||||
_ = s.read_byte()!
|
||||
break
|
||||
} else if escaped := important_escapable_char(ch) {
|
||||
return new_scan_error('character must be escaped with a backslash, replace with: \\${escaped.ascii_str()}',
|
||||
current_line, current_col)
|
||||
} else if ch < 0x20 {
|
||||
return new_scan_error('character must be escaped with a unicode escape, replace with: \\u${ch:04x}',
|
||||
current_line, current_col)
|
||||
} else if ch == `\\` {
|
||||
_ = s.read_byte()!
|
||||
escape_line, escape_col := s.line, s.col
|
||||
if !s.has_next_byte()! {
|
||||
return new_scan_error('incomplete backslash escape at end of JSON input',
|
||||
escape_line, escape_col)
|
||||
}
|
||||
peek := s.ch
|
||||
if peek in valid_unicode_escapes {
|
||||
chrs << unicode_transform_escapes[int(peek)]
|
||||
_ = s.read_byte()!
|
||||
continue
|
||||
} else if peek == `u` {
|
||||
_ = s.read_byte()!
|
||||
mut codepoint := []u8{}
|
||||
for _ in 0 .. 4 {
|
||||
digit_line, digit_col := s.line, s.col
|
||||
if !s.has_next_byte()! {
|
||||
return new_scan_error('incomplete unicode escape', escape_line, escape_col)
|
||||
}
|
||||
digit := s.ch
|
||||
if digit == `"` {
|
||||
return new_scan_error('unicode escape must have 4 hex digits', digit_line,
|
||||
digit_col)
|
||||
} else if !digit.is_hex_digit() {
|
||||
x := digit.ascii_str()
|
||||
return new_scan_error('`${x}` is not a hex digit', digit_line, digit_col)
|
||||
}
|
||||
codepoint << digit
|
||||
_ = s.read_byte()!
|
||||
}
|
||||
val := u32(strconv.parse_uint(codepoint.bytestr(), 16, 32) or { 0 })
|
||||
converted := utf32_to_str(val)
|
||||
converted_bytes := converted.bytes()
|
||||
chrs << converted_bytes
|
||||
unsafe {
|
||||
converted.free()
|
||||
converted_bytes.free()
|
||||
codepoint.free()
|
||||
}
|
||||
continue
|
||||
} else if peek == `U` {
|
||||
return new_scan_error('unicode endpoints must be in lowercase `u`', escape_line,
|
||||
escape_col)
|
||||
} else if peek == u8(229) {
|
||||
return new_scan_error('unicode endpoint not allowed', escape_line, escape_col)
|
||||
} else {
|
||||
return new_scan_error('invalid backslash escape', escape_line, escape_col)
|
||||
}
|
||||
}
|
||||
chrs << ch
|
||||
_ = s.read_byte()!
|
||||
}
|
||||
return s.tokenize(chrs, .str, line, col)
|
||||
}
|
||||
|
||||
fn (mut s ReaderScanner) num_scan(line int, col int) !Token {
|
||||
mut is_fl := false
|
||||
mut dot_index := -1
|
||||
mut digits := []u8{}
|
||||
if s.peek_byte()! == `-` {
|
||||
digits << `-`
|
||||
_ = s.read_byte()!
|
||||
if !s.has_next_byte()! {
|
||||
return new_scan_error('invalid token `-`', line, col)
|
||||
}
|
||||
next := s.ch
|
||||
if !next.is_digit() {
|
||||
return new_scan_error(invalid_token_description(next), s.line, s.col)
|
||||
}
|
||||
}
|
||||
if s.has_next_byte()! {
|
||||
first := s.ch
|
||||
if first == `0` {
|
||||
digits << first
|
||||
_ = s.read_byte()!
|
||||
if s.has_next_byte()! && s.ch.is_digit() {
|
||||
return new_scan_error('leading zeroes in a number are not allowed', line, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
if !s.has_next_byte()! {
|
||||
break
|
||||
}
|
||||
ch := s.ch
|
||||
if ch.is_digit() || (!is_fl && ch == `.`) {
|
||||
digits << ch
|
||||
if ch == `.` {
|
||||
is_fl = true
|
||||
dot_index = digits.len - 1
|
||||
}
|
||||
_ = s.read_byte()!
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if dot_index != -1 && digits[dot_index + 1..].len == 0 {
|
||||
return new_scan_error('invalid float', line, col)
|
||||
}
|
||||
if s.has_next_byte()! {
|
||||
ch := s.ch
|
||||
if ch == `e` || ch == `E` {
|
||||
digits << ch
|
||||
_ = s.read_byte()!
|
||||
if s.has_next_byte()! && s.ch in exp_signs {
|
||||
digits << s.ch
|
||||
_ = s.read_byte()!
|
||||
}
|
||||
mut exp_digits_count := 0
|
||||
for {
|
||||
if !s.has_next_byte()! {
|
||||
break
|
||||
}
|
||||
digit := s.ch
|
||||
if !digit.is_digit() {
|
||||
break
|
||||
}
|
||||
digits << digit
|
||||
exp_digits_count++
|
||||
_ = s.read_byte()!
|
||||
}
|
||||
if exp_digits_count == 0 {
|
||||
return new_scan_error('invalid exponent', line, col)
|
||||
}
|
||||
}
|
||||
}
|
||||
kind := if is_fl { TokenKind.float } else { TokenKind.int }
|
||||
return s.tokenize(digits, kind, line, col)
|
||||
}
|
||||
|
||||
// next returns the next JSON token from the reader-backed scanner.
|
||||
pub fn (mut s ReaderScanner) next() !Token {
|
||||
s.skip_whitespace()!
|
||||
line, col := s.line, s.col
|
||||
if !s.has_next_byte()! {
|
||||
return s.tokenize([]u8{}, .eof, line, col)
|
||||
}
|
||||
ch := s.ch
|
||||
if ch == `t` || ch == `n` {
|
||||
ident := if ch == `t` { 'true' } else { 'null' }
|
||||
kind := if ch == `t` { TokenKind.bool } else { TokenKind.null }
|
||||
return s.scan_ident(ident, kind, line, col)
|
||||
} else if ch == `f` {
|
||||
return s.scan_ident('false', .bool, line, col)
|
||||
} else if ch in char_list {
|
||||
_ = s.read_byte()!
|
||||
return s.tokenize([]u8{}, unsafe { TokenKind(int(ch)) }, line, col)
|
||||
} else if ch == `"` {
|
||||
return s.text_scan(line, col)
|
||||
} else if ch.is_digit() || ch == `-` {
|
||||
return s.num_scan(line, col)
|
||||
}
|
||||
return new_scan_error(invalid_token_description(ch), line, col)
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
module json2
|
||||
|
||||
fn test_str() {
|
||||
mut sc := Scanner{
|
||||
text: '"test"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .str
|
||||
assert tok.lit.len == 4
|
||||
assert tok.lit.bytestr() == 'test'
|
||||
}
|
||||
|
||||
fn test_str_valid_unicode_escape() {
|
||||
mut sc := Scanner{
|
||||
text: r'"\u0048"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .str
|
||||
assert tok.lit.len == 1
|
||||
assert tok.lit.bytestr() == 'H'
|
||||
}
|
||||
|
||||
fn test_str_valid_unicode_escape_2() {
|
||||
mut sc := Scanner{
|
||||
text: r'"\u2714"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .str
|
||||
assert tok.lit.len == 3
|
||||
assert tok.lit.bytestr() == '✔'
|
||||
}
|
||||
|
||||
fn test_str_valid_escaped_backslashes_before_string_end() {
|
||||
mut sc := Scanner{
|
||||
text: r'"some_value \n [ ] { } ( ) , ; ? * = ! \\@ \\"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .str
|
||||
assert tok.lit.bytestr() == 'some_value \n [ ] { } ( ) , ; ? * = ! \\@ \\'
|
||||
}
|
||||
|
||||
fn test_str_invalid_escape() {
|
||||
mut sc := Scanner{
|
||||
text: r'"\z"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid backslash escape'
|
||||
}
|
||||
|
||||
fn test_str_invalid_must_be_escape() {
|
||||
for ch in important_escapable_chars {
|
||||
mut sc := Scanner{
|
||||
text: [u8(`"`), `t`, ch, `"`]
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'character must be escaped with a backslash, replace with: \\${valid_unicode_escapes[important_escapable_chars.index(ch)]}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_str_control_must_be_escape() {
|
||||
for ch := u8(0); ch < 0x20; ch++ {
|
||||
if ch in important_escapable_chars {
|
||||
continue
|
||||
}
|
||||
mut sc := Scanner{
|
||||
text: [u8(`"`), `t`, ch, `"`]
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'character must be escaped with a unicode escape, replace with: \\u${ch:04x}'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_str_invalid_unicode_escape() {
|
||||
mut sc := Scanner{
|
||||
text: r'"\u010G"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == '`G` is not a hex digit'
|
||||
}
|
||||
|
||||
fn test_str_invalid_unicode_escape_len() {
|
||||
mut sc := Scanner{
|
||||
text: r'"\u001"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'unicode escape must have 4 hex digits'
|
||||
}
|
||||
|
||||
fn test_str_invalid_uppercase_u() {
|
||||
mut sc := Scanner{
|
||||
text: r'"\U0000"'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'unicode endpoints must be in lowercase `u`'
|
||||
}
|
||||
|
||||
fn test_str_missing_closing_bracket() {
|
||||
mut sc := Scanner{
|
||||
text: '"incomplete'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'missing double quotes in string closing'
|
||||
}
|
||||
|
||||
fn test_int() {
|
||||
mut sc := Scanner{
|
||||
text: '10'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .int
|
||||
assert tok.lit.len == 2
|
||||
assert tok.lit.bytestr() == '10'
|
||||
}
|
||||
|
||||
fn test_int_negative() {
|
||||
mut sc := Scanner{
|
||||
text: '-10'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .int
|
||||
assert tok.lit.len == 3
|
||||
assert tok.lit.bytestr() == '-10'
|
||||
}
|
||||
|
||||
fn test_float() {
|
||||
mut sc := Scanner{
|
||||
text: '123.400'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .float
|
||||
assert tok.lit.len == 7
|
||||
assert tok.lit.bytestr() == '123.400'
|
||||
}
|
||||
|
||||
fn test_float_negative() {
|
||||
mut sc := Scanner{
|
||||
text: '-123.400'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .float
|
||||
assert tok.lit.len == 8
|
||||
assert tok.lit.bytestr() == '-123.400'
|
||||
}
|
||||
|
||||
fn test_int_exp() {
|
||||
mut sc := Scanner{
|
||||
text: '1E22'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .int
|
||||
assert tok.lit.len == 4
|
||||
assert tok.lit.bytestr() == '1E22'
|
||||
}
|
||||
|
||||
fn test_int_exp_negative() {
|
||||
mut sc := Scanner{
|
||||
text: '1E-2'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .int
|
||||
assert tok.lit.len == 4
|
||||
assert tok.lit.bytestr() == '1E-2'
|
||||
}
|
||||
|
||||
fn test_int_exp_positive() {
|
||||
mut sc := Scanner{
|
||||
text: '1E+2'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .int
|
||||
assert tok.lit.len == 4
|
||||
assert tok.lit.bytestr() == '1E+2'
|
||||
}
|
||||
|
||||
fn test_float_exp() {
|
||||
mut sc := Scanner{
|
||||
text: '123.456e78'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .float
|
||||
assert tok.lit.len == 10
|
||||
assert tok.lit.bytestr() == '123.456e78'
|
||||
}
|
||||
|
||||
fn test_float_exp_negative() {
|
||||
mut sc := Scanner{
|
||||
text: '20.56e-5'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .float
|
||||
assert tok.lit.len == 8
|
||||
assert tok.lit.bytestr() == '20.56e-5'
|
||||
}
|
||||
|
||||
fn test_float_exp_positive() {
|
||||
mut sc := Scanner{
|
||||
text: '20.56e+5'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .float
|
||||
assert tok.lit.len == 8
|
||||
assert tok.lit.bytestr() == '20.56e+5'
|
||||
}
|
||||
|
||||
fn test_number_with_space() {
|
||||
mut sc := Scanner{
|
||||
text: ' 4'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .int
|
||||
assert tok.lit.len == 1
|
||||
assert tok.lit.bytestr() == '4'
|
||||
}
|
||||
|
||||
fn test_number_invalid_leading_zero() {
|
||||
mut sc := Scanner{
|
||||
text: '0010'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'leading zeroes in a number are not allowed'
|
||||
}
|
||||
|
||||
fn test_number_invalid_leading_zero_negative() {
|
||||
mut sc := Scanner{
|
||||
text: '-0010'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'leading zeroes in a number are not allowed'
|
||||
}
|
||||
|
||||
fn test_number_invalid_start_char() {
|
||||
mut sc := Scanner{
|
||||
text: '+1'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid token `+`'
|
||||
}
|
||||
|
||||
fn test_number_invalid_char() {
|
||||
mut sc := Scanner{
|
||||
text: '122x'.bytes()
|
||||
}
|
||||
sc.scan()
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid token `x`'
|
||||
}
|
||||
|
||||
fn test_number_invalid_char_float() {
|
||||
mut sc := Scanner{
|
||||
text: '122x.1'.bytes()
|
||||
}
|
||||
sc.scan()
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid token `x`'
|
||||
}
|
||||
|
||||
fn test_number_invalid_multiple_dot() {
|
||||
mut sc := Scanner{
|
||||
text: '122.108.10'.bytes()
|
||||
}
|
||||
sc.scan()
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid token `.`'
|
||||
}
|
||||
|
||||
fn test_number_invalid_exp() {
|
||||
mut sc := Scanner{
|
||||
text: '0.3e'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid exponent'
|
||||
}
|
||||
|
||||
fn test_number_invalid_exp_with_sign() {
|
||||
mut sc := Scanner{
|
||||
text: '0.3e+'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid exponent'
|
||||
}
|
||||
|
||||
fn test_number_invalid_zero_exp() {
|
||||
mut sc := Scanner{
|
||||
text: '0e'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid exponent'
|
||||
}
|
||||
|
||||
fn test_number_invalid_dot_exp() {
|
||||
mut sc := Scanner{
|
||||
text: '0.e'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid float'
|
||||
}
|
||||
|
||||
fn test_number_invalid_double_exp() {
|
||||
mut sc := Scanner{
|
||||
text: '2eE'.bytes()
|
||||
}
|
||||
sc.scan()
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .error
|
||||
assert tok.lit.bytestr() == 'invalid token `E`'
|
||||
}
|
||||
|
||||
fn test_null() {
|
||||
mut sc := Scanner{
|
||||
text: 'null'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .null
|
||||
assert tok.lit.len == 4
|
||||
assert tok.lit.bytestr() == 'null'
|
||||
}
|
||||
|
||||
fn test_bool_true() {
|
||||
mut sc := Scanner{
|
||||
text: 'true'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .bool
|
||||
assert tok.lit.len == 4
|
||||
assert tok.lit.bytestr() == 'true'
|
||||
}
|
||||
|
||||
fn test_bool_false() {
|
||||
mut sc := Scanner{
|
||||
text: 'false'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .bool
|
||||
assert tok.lit.len == 5
|
||||
assert tok.lit.bytestr() == 'false'
|
||||
}
|
||||
|
||||
fn test_json_with_whitespace_start() {
|
||||
mut sc := Scanner{
|
||||
text: ' \n \n\t {'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
eprintln(tok)
|
||||
assert tok.kind == .lcbr
|
||||
assert tok.lit.len == 0
|
||||
}
|
||||
|
||||
fn test_json_with_whitespace_end() {
|
||||
mut sc := Scanner{
|
||||
text: '} \n\t'.bytes()
|
||||
}
|
||||
tok := sc.scan()
|
||||
assert tok.kind == .rcbr
|
||||
tok2 := sc.scan()
|
||||
eprintln(tok2)
|
||||
assert tok2.kind == .eof
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
module strict
|
||||
|
||||
import arrays
|
||||
|
||||
pub struct KeyStruct {
|
||||
pub:
|
||||
key string
|
||||
value_type KeyType
|
||||
token_pos int // the position of the token
|
||||
}
|
||||
|
||||
pub enum KeyType {
|
||||
literal
|
||||
map
|
||||
array
|
||||
}
|
||||
|
||||
pub struct StructCheckResult {
|
||||
pub:
|
||||
duplicates []string
|
||||
superfluous []string
|
||||
}
|
||||
|
||||
// strict_check .
|
||||
pub fn strict_check[T](json_data string) StructCheckResult {
|
||||
// REVIEW how performatic is it?
|
||||
$if T is $struct {
|
||||
tokens := tokenize(json_data)
|
||||
|
||||
key_struct := get_keys_from_json(tokens)
|
||||
|
||||
mut duplicates := get_duplicates_keys(key_struct)
|
||||
mut superfluous := get_superfluous_keys[T](key_struct)
|
||||
|
||||
mut val := T{}
|
||||
|
||||
$for field in T.fields {
|
||||
$if field.typ is $struct {
|
||||
last_key := find_last_key(key_struct, field.name) or {
|
||||
panic('field not found: ' + field.name)
|
||||
}
|
||||
|
||||
// TODO: get path here from `last_key.key`
|
||||
if last_key.value_type == .map {
|
||||
check(val.$(field.name), tokens[last_key.token_pos + 2..], mut duplicates, mut
|
||||
superfluous)
|
||||
}
|
||||
}
|
||||
}
|
||||
return StructCheckResult{
|
||||
duplicates: duplicates
|
||||
superfluous: superfluous
|
||||
}
|
||||
} $else {
|
||||
return StructCheckResult{}
|
||||
}
|
||||
}
|
||||
|
||||
fn check[T](val T, tokens []string, mut duplicates []string, mut superfluous []string) {
|
||||
$if T is $struct {
|
||||
key_struct := get_keys_from_json(tokens)
|
||||
|
||||
for duplicate in get_duplicates_keys(key_struct) {
|
||||
duplicates << duplicate
|
||||
}
|
||||
|
||||
for unnecessary in get_superfluous_keys[T](key_struct) {
|
||||
superfluous << unnecessary
|
||||
}
|
||||
|
||||
$for field in T.fields {
|
||||
$if field.typ is $struct {
|
||||
last_key := find_last_key(key_struct, field.name) or {
|
||||
panic('field not found: ' + field.name)
|
||||
}
|
||||
if last_key.value_type == .map {
|
||||
check(val.$(field.name), tokens[last_key.token_pos + 2..], mut duplicates, mut
|
||||
superfluous)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_superfluous_keys[T](key_struct []KeyStruct) []string {
|
||||
mut superfluous := []string{}
|
||||
|
||||
struct_keys := get_keys_from_[T]()
|
||||
|
||||
json_keys := key_struct.map(it.key)
|
||||
|
||||
for json_key in json_keys {
|
||||
if !struct_keys.contains(json_key) {
|
||||
superfluous << json_key
|
||||
}
|
||||
}
|
||||
return superfluous
|
||||
}
|
||||
|
||||
fn get_duplicates_keys(key_struct []KeyStruct) []string {
|
||||
json_keys := key_struct.map(it.key).sorted()
|
||||
return arrays.uniq_only_repeated(json_keys)
|
||||
}
|
||||
|
||||
fn find_last_key(key_struct []KeyStruct, field_name string) ?KeyStruct {
|
||||
for idx := key_struct.len; idx > 0; idx-- {
|
||||
item := key_struct[idx - 1]
|
||||
if item.key == field_name {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn get_keys_from_[T]() []string {
|
||||
mut struct_keys := []string{}
|
||||
$if T is $struct {
|
||||
$for field in T.fields {
|
||||
struct_keys << field.name
|
||||
}
|
||||
}
|
||||
return struct_keys
|
||||
}
|
||||
|
||||
// get_keys_from_json .
|
||||
pub fn get_keys_from_json(tokens []string) []KeyStruct {
|
||||
mut key_structs := []KeyStruct{}
|
||||
|
||||
mut nested_map_count := 0
|
||||
|
||||
for i, token in tokens {
|
||||
if token == ':' {
|
||||
mut current_key := tokens[i - 1].replace('"', '')
|
||||
if tokens[i + 1] == '{' {
|
||||
if nested_map_count == 0 {
|
||||
key_type := KeyType.map
|
||||
key_structs << KeyStruct{
|
||||
key: current_key
|
||||
value_type: key_type
|
||||
token_pos: i - 1
|
||||
}
|
||||
}
|
||||
nested_map_count++
|
||||
} else if tokens[i + 1] == '[' {
|
||||
continue
|
||||
} else if nested_map_count > 0 {
|
||||
if tokens[i + 1] == '}' {
|
||||
nested_map_count--
|
||||
} else {
|
||||
// REVIEW Não sei
|
||||
}
|
||||
} else {
|
||||
key_type := KeyType.literal
|
||||
key_structs << KeyStruct{
|
||||
key: current_key
|
||||
value_type: key_type
|
||||
token_pos: i - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return key_structs
|
||||
}
|
||||
|
||||
fn tokenize(json_data string) []string {
|
||||
mut tokens := []string{}
|
||||
mut current_token := ''
|
||||
mut inside_string := false
|
||||
|
||||
for letter in json_data.replace('\n', ' ').replace('\t', ' ') {
|
||||
if letter == ` ` && !inside_string {
|
||||
if current_token != '' {
|
||||
tokens << current_token
|
||||
current_token = ''
|
||||
}
|
||||
} else if letter == `\"` {
|
||||
inside_string = !inside_string
|
||||
current_token += '"'
|
||||
} else if letter == `,` || letter == `:` || letter == `{` || letter == `}` || letter == `[`
|
||||
|| letter == `]` {
|
||||
if current_token != '' {
|
||||
tokens << current_token
|
||||
current_token = ''
|
||||
}
|
||||
tokens << [letter].bytestr()
|
||||
} else {
|
||||
current_token += [letter].bytestr()
|
||||
}
|
||||
}
|
||||
|
||||
if current_token != '' {
|
||||
tokens << current_token
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import x.json2.strict
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeAndOptionType[T] {
|
||||
mut:
|
||||
val T
|
||||
option_val ?T
|
||||
}
|
||||
|
||||
fn test_get_keys_from_json() {
|
||||
json_data := r'
|
||||
{
|
||||
"val": 0,
|
||||
"val1": {"val": 63}
|
||||
}
|
||||
'
|
||||
|
||||
key_structs := strict.get_keys_from_json(strict.tokenize(json_data))
|
||||
|
||||
assert key_structs == [
|
||||
strict.KeyStruct{
|
||||
key: 'val'
|
||||
value_type: .literal
|
||||
token_pos: 1
|
||||
},
|
||||
strict.KeyStruct{
|
||||
key: 'val1'
|
||||
value_type: .map
|
||||
token_pos: 5
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn test_strict_check() {
|
||||
assert strict.strict_check[StructTypeAndOptionType[string]]('{"val": "","val": ""}') == strict.StructCheckResult{
|
||||
duplicates: ['val']
|
||||
superfluous: []
|
||||
}
|
||||
|
||||
assert strict.strict_check[StructTypeAndOptionType[string]]('{"val": "","val2": ""}') == strict.StructCheckResult{
|
||||
duplicates: []
|
||||
superfluous: ['val2']
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import x.json2.strict
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeAndOptionType[T] {
|
||||
mut:
|
||||
val T
|
||||
option_val ?T
|
||||
}
|
||||
|
||||
fn test_strict_check() {
|
||||
assert strict.strict_check[StructTypeAndOptionType[string]]('{"val": "","val2": "","val3": "","val3": ""}') == strict.StructCheckResult{
|
||||
duplicates: ['val3']
|
||||
superfluous: ['val2', 'val3']
|
||||
}
|
||||
|
||||
assert strict.strict_check[StructType[StructTypeAndOptionType[string]]]('{"val": {"val": "","val2": ""}}') == strict.StructCheckResult{
|
||||
duplicates: []
|
||||
superfluous: ['val.val2']
|
||||
}
|
||||
|
||||
assert strict.strict_check[StructType[[]StructTypeAndOptionType[string]]]('{"val": [{"val": "","val2": ""}],[{"val": "","gdgd": "sss"}]}') == strict.StructCheckResult{
|
||||
duplicates: []
|
||||
superfluous: ['val[0].val2', 'val[1].gdgd']
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
import x.json2 as json
|
||||
|
||||
const sample_data = {
|
||||
'u8': json.Any(u8(1))
|
||||
'u16': json.Any(u16(2))
|
||||
'u32': json.Any(u32(3))
|
||||
'u64': json.Any(u64(4))
|
||||
'i8': json.Any(i8(5))
|
||||
'i16': json.Any(i16(6))
|
||||
'i32': json.Any(i32(7))
|
||||
'int': json.Any(int(8))
|
||||
'i64': json.Any(i64(9))
|
||||
'f32': json.Any(f32(2.3))
|
||||
'f64': json.Any(f64(1.283))
|
||||
'bool': json.Any(false)
|
||||
'str': json.Any('test')
|
||||
'null': json.Any(json.null)
|
||||
'arr': json.Any([json.Any('lol')])
|
||||
'obj': json.Any({
|
||||
'foo': json.Any(10)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_null(f json.Any) bool {
|
||||
match f {
|
||||
json.Null { return true }
|
||||
else { return false }
|
||||
}
|
||||
}
|
||||
|
||||
fn test_f32() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.f32() == 1.0
|
||||
assert sample_data['u16'] or { 0 }.f32() == 2.0
|
||||
assert sample_data['u32'] or { 0 }.f32() == 3.0
|
||||
assert sample_data['u64'] or { 0 }.f32() == 4.0
|
||||
assert sample_data['i8'] or { 0 }.f32() == 5.0
|
||||
assert sample_data['i16'] or { 0 }.f32() == 6.0
|
||||
assert sample_data['i32'] or { 0 }.f32() == 7.0
|
||||
assert sample_data['int'] or { 0 }.f32() == 8.0
|
||||
assert sample_data['i64'] or { 0 }.f32() == 9.0
|
||||
assert sample_data['f32'] or { 0 }.f32() == 2.3
|
||||
assert sample_data['f64'] or { 0 }.f32() == 1.2829999923706055
|
||||
// invalid conversions
|
||||
assert sample_data['bool'] or { 0 }.f32() == 0.0
|
||||
assert sample_data['str'] or { 0 }.f32() == 0.0
|
||||
assert sample_data['null'] or { 0 }.f32() == 0.0
|
||||
assert sample_data['arr'] or { 0 }.f32() == 0.0
|
||||
assert sample_data['obj'] or { 0 }.f32() == 0.0
|
||||
}
|
||||
|
||||
fn test_f64() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.f64() == 1.0
|
||||
assert sample_data['u16'] or { 0 }.f64() == 2.0
|
||||
assert sample_data['u32'] or { 0 }.f64() == 3.0
|
||||
assert sample_data['u64'] or { 0 }.f64() == 4.0
|
||||
assert sample_data['i8'] or { 0 }.f64() == 5.0
|
||||
assert sample_data['i16'] or { 0 }.f64() == 6.0
|
||||
assert sample_data['i32'] or { 0 }.f64() == 7.0
|
||||
assert sample_data['int'] or { 0 }.f64() == 8.0
|
||||
assert sample_data['i64'] or { 0 }.f64() == 9.0
|
||||
assert sample_data['f32'] or { 0 }.f64() == 2.299999952316284
|
||||
assert sample_data['f64'] or { 0 }.f64() == 1.283
|
||||
// invalid conversions
|
||||
assert sample_data['bool'] or { 0 }.f64() == 0.0
|
||||
assert sample_data['str'] or { 0 }.f64() == 0.0
|
||||
assert sample_data['null'] or { 0 }.f64() == 0.0
|
||||
assert sample_data['arr'] or { 0 }.f64() == 0.0
|
||||
assert sample_data['obj'] or { 0 }.f64() == 0.0
|
||||
}
|
||||
|
||||
fn test_i8() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.i8() == 1
|
||||
assert sample_data['u16'] or { 0 }.i8() == 2
|
||||
assert sample_data['u32'] or { 0 }.i8() == 3
|
||||
assert sample_data['u64'] or { 0 }.i8() == 4
|
||||
assert sample_data['i8'] or { 0 }.i8() == 5
|
||||
assert sample_data['i16'] or { 0 }.i8() == 6
|
||||
assert sample_data['i32'] or { 0 }.i8() == 7
|
||||
assert sample_data['int'] or { 0 }.i8() == 8
|
||||
assert sample_data['i64'] or { 0 }.i8() == 9
|
||||
assert sample_data['f32'] or { 0 }.i8() == 2
|
||||
assert sample_data['f64'] or { 0 }.i8() == 1
|
||||
assert json.Any(true).i8() == 1
|
||||
assert json.Any('123').i8() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.i8() == 0
|
||||
assert sample_data['arr'] or { 0 }.i8() == 0
|
||||
assert sample_data['obj'] or { 0 }.i8() == 0
|
||||
}
|
||||
|
||||
fn test_i16() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.i16() == 1
|
||||
assert sample_data['u16'] or { 0 }.i16() == 2
|
||||
assert sample_data['u32'] or { 0 }.i16() == 3
|
||||
assert sample_data['u64'] or { 0 }.i16() == 4
|
||||
assert sample_data['i8'] or { 0 }.i16() == 5
|
||||
assert sample_data['i16'] or { 0 }.i16() == 6
|
||||
assert sample_data['i32'] or { 0 }.i16() == 7
|
||||
assert sample_data['int'] or { 0 }.i16() == 8
|
||||
assert sample_data['i64'] or { 0 }.i16() == 9
|
||||
assert sample_data['f32'] or { 0 }.i16() == 2
|
||||
assert sample_data['f64'] or { 0 }.i16() == 1
|
||||
assert json.Any(true).i16() == 1
|
||||
assert json.Any('123').i16() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.i16() == 0
|
||||
assert sample_data['arr'] or { 0 }.i16() == 0
|
||||
assert sample_data['obj'] or { 0 }.i16() == 0
|
||||
}
|
||||
|
||||
fn test_i32() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.i32() == 1
|
||||
assert sample_data['u16'] or { 0 }.i32() == 2
|
||||
assert sample_data['u32'] or { 0 }.i32() == 3
|
||||
assert sample_data['u64'] or { 0 }.i32() == 4
|
||||
assert sample_data['i8'] or { 0 }.i32() == 5
|
||||
assert sample_data['i16'] or { 0 }.i32() == 6
|
||||
assert sample_data['i32'] or { 0 }.i32() == 7
|
||||
assert sample_data['int'] or { 0 }.i32() == 8
|
||||
assert sample_data['i64'] or { 0 }.i32() == 9
|
||||
assert sample_data['f32'] or { 0 }.i32() == 2
|
||||
assert sample_data['f64'] or { 0 }.i32() == 1
|
||||
assert json.Any(true).i32() == 1
|
||||
assert json.Any('123').i32() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.i32() == 0
|
||||
assert sample_data['arr'] or { 0 }.i32() == 0
|
||||
assert sample_data['obj'] or { 0 }.i32() == 0
|
||||
}
|
||||
|
||||
fn test_int() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.int() == 1
|
||||
assert sample_data['u16'] or { 0 }.int() == 2
|
||||
assert sample_data['u32'] or { 0 }.int() == 3
|
||||
assert sample_data['u64'] or { 0 }.int() == 4
|
||||
assert sample_data['i8'] or { 0 }.int() == 5
|
||||
assert sample_data['i16'] or { 0 }.int() == 6
|
||||
assert sample_data['i32'] or { 0 }.int() == 7
|
||||
assert sample_data['int'] or { 0 }.int() == 8
|
||||
assert sample_data['i64'] or { 0 }.int() == 9
|
||||
assert sample_data['f32'] or { 0 }.int() == 2
|
||||
assert sample_data['f64'] or { 0 }.int() == 1
|
||||
assert json.Any(true).int() == 1
|
||||
assert json.Any('123').int() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.int() == 0
|
||||
assert sample_data['arr'] or { 0 }.int() == 0
|
||||
assert sample_data['obj'] or { 0 }.int() == 0
|
||||
}
|
||||
|
||||
fn test_i64() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.i64() == 1
|
||||
assert sample_data['u16'] or { 0 }.i64() == 2
|
||||
assert sample_data['u32'] or { 0 }.i64() == 3
|
||||
assert sample_data['u64'] or { 0 }.i64() == 4
|
||||
assert sample_data['i8'] or { 0 }.i64() == 5
|
||||
assert sample_data['i16'] or { 0 }.i64() == 6
|
||||
assert sample_data['i32'] or { 0 }.i64() == 7
|
||||
assert sample_data['int'] or { 0 }.i64() == 8
|
||||
assert sample_data['i64'] or { 0 }.i64() == 9
|
||||
assert sample_data['f32'] or { 0 }.i64() == 2
|
||||
assert sample_data['f64'] or { 0 }.i64() == 1
|
||||
assert json.Any(true).i64() == 1
|
||||
assert json.Any('123').i64() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.i64() == 0
|
||||
assert sample_data['arr'] or { 0 }.i64() == 0
|
||||
assert sample_data['obj'] or { 0 }.i64() == 0
|
||||
}
|
||||
|
||||
fn test_u8() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.u8() == 1
|
||||
assert sample_data['u16'] or { 0 }.u8() == 2
|
||||
assert sample_data['u32'] or { 0 }.u8() == 3
|
||||
assert sample_data['u64'] or { 0 }.u8() == 4
|
||||
assert sample_data['i8'] or { 0 }.u8() == 5
|
||||
assert sample_data['i16'] or { 0 }.u8() == 6
|
||||
assert sample_data['i32'] or { 0 }.u8() == 7
|
||||
assert sample_data['int'] or { 0 }.u8() == 8
|
||||
assert sample_data['i64'] or { 0 }.u8() == 9
|
||||
assert sample_data['f32'] or { 0 }.u8() == 2
|
||||
assert sample_data['f64'] or { 0 }.u8() == 1
|
||||
assert json.Any(true).u8() == 1
|
||||
assert json.Any('123').u8() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.u8() == 0
|
||||
assert sample_data['arr'] or { 0 }.u8() == 0
|
||||
assert sample_data['obj'] or { 0 }.u8() == 0
|
||||
}
|
||||
|
||||
fn test_u16() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.u16() == 1
|
||||
assert sample_data['u16'] or { 0 }.u16() == 2
|
||||
assert sample_data['u32'] or { 0 }.u16() == 3
|
||||
assert sample_data['u64'] or { 0 }.u16() == 4
|
||||
assert sample_data['i8'] or { 0 }.u16() == 5
|
||||
assert sample_data['i16'] or { 0 }.u16() == 6
|
||||
assert sample_data['i32'] or { 0 }.u16() == 7
|
||||
assert sample_data['int'] or { 0 }.u16() == 8
|
||||
assert sample_data['i64'] or { 0 }.u16() == 9
|
||||
assert sample_data['f32'] or { 0 }.u16() == 2
|
||||
assert sample_data['f64'] or { 0 }.u16() == 1
|
||||
assert json.Any(true).u16() == 1
|
||||
assert json.Any('123').u16() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.u16() == 0
|
||||
assert sample_data['arr'] or { 0 }.u16() == 0
|
||||
assert sample_data['obj'] or { 0 }.u16() == 0
|
||||
}
|
||||
|
||||
fn test_u32() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.u32() == 1
|
||||
assert sample_data['u16'] or { 0 }.u32() == 2
|
||||
assert sample_data['u32'] or { 0 }.u32() == 3
|
||||
assert sample_data['u64'] or { 0 }.u32() == 4
|
||||
assert sample_data['i8'] or { 0 }.u32() == 5
|
||||
assert sample_data['i16'] or { 0 }.u32() == 6
|
||||
assert sample_data['i32'] or { 0 }.u32() == 7
|
||||
assert sample_data['int'] or { 0 }.u32() == 8
|
||||
assert sample_data['i64'] or { 0 }.u32() == 9
|
||||
assert sample_data['f32'] or { 0 }.u32() == 2
|
||||
assert sample_data['f64'] or { 0 }.u32() == 1
|
||||
assert json.Any(true).u32() == 1
|
||||
assert json.Any('123').u32() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.u32() == 0
|
||||
assert sample_data['arr'] or { 0 }.u32() == 0
|
||||
assert sample_data['obj'] or { 0 }.u32() == 0
|
||||
}
|
||||
|
||||
fn test_u64() {
|
||||
// valid conversions
|
||||
assert sample_data['u8'] or { 0 }.u64() == 1
|
||||
assert sample_data['u16'] or { 0 }.u64() == 2
|
||||
assert sample_data['u32'] or { 0 }.u64() == 3
|
||||
assert sample_data['u64'] or { 0 }.u64() == 4
|
||||
assert sample_data['i8'] or { 0 }.u64() == 5
|
||||
assert sample_data['i16'] or { 0 }.u64() == 6
|
||||
assert sample_data['i32'] or { 0 }.u64() == 7
|
||||
assert sample_data['int'] or { 0 }.u64() == 8
|
||||
assert sample_data['i64'] or { 0 }.u64() == 9
|
||||
assert sample_data['f32'] or { 0 }.u64() == 2
|
||||
assert sample_data['f64'] or { 0 }.u64() == 1
|
||||
assert json.Any(true).u64() == 1
|
||||
assert json.Any('123').u64() == 123
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.u64() == 0
|
||||
assert sample_data['arr'] or { 0 }.u64() == 0
|
||||
assert sample_data['obj'] or { 0 }.u64() == 0
|
||||
}
|
||||
|
||||
fn test_as_map() {
|
||||
assert sample_data['u8'] or { 0 }.as_map()['0'] or { 0 }.u8() == 1
|
||||
assert sample_data['u16'] or { 0 }.as_map()['0'] or { 0 }.u16() == 2
|
||||
assert sample_data['u32'] or { 0 }.as_map()['0'] or { 0 }.u32() == 3
|
||||
assert sample_data['u64'] or { 0 }.as_map()['0'] or { 0 }.u64() == 4
|
||||
assert sample_data['i8'] or { 0 }.as_map()['0'] or { 0 }.i8() == 5
|
||||
assert sample_data['i16'] or { 0 }.as_map()['0'] or { 0 }.i16() == 6
|
||||
assert sample_data['i32'] or { 0 }.as_map()['0'] or { 0 }.i32() == 7
|
||||
assert sample_data['int'] or { 0 }.as_map()['0'] or { 0 }.int() == 8
|
||||
assert sample_data['i64'] or { 0 }.as_map()['0'] or { 0 }.i64() == 9
|
||||
assert sample_data['f32'] or { 0 }.as_map()['0'] or { 0 }.f32() == 2.3
|
||||
assert sample_data['f64'] or { 0 }.as_map()['0'] or { 0 }.f64() == 1.283
|
||||
assert sample_data['bool'] or { 0 }.as_map()['0'] or { 0 }.bool() == false
|
||||
assert sample_data['str'] or { 0 }.as_map()['0'] or { 0 }.str() == 'test'
|
||||
assert is_null(sample_data['null'] or { 0 }.as_map()['0'] or { 0 }) == true
|
||||
assert sample_data['arr'] or { 0 }.as_map()['0'] or { 0 }.str() == 'lol'
|
||||
assert sample_data['obj'] or { 0 }.as_map()['foo'] or { 0 }.int() == 10
|
||||
}
|
||||
|
||||
fn test_as_map_of_strings() {
|
||||
assert sample_data['obj']!.as_map() == {
|
||||
'foo': json.Any(10)
|
||||
}
|
||||
assert sample_data['obj']!.as_map_of_strings() == {
|
||||
'foo': '10'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_as_array() {
|
||||
assert sample_data['u8'] or { 0 }.as_array()[0].u8() == 1
|
||||
assert sample_data['u16'] or { 0 }.as_array()[0].u16() == 2
|
||||
assert sample_data['u32'] or { 0 }.as_array()[0].u32() == 3
|
||||
assert sample_data['u64'] or { 0 }.as_array()[0].u64() == 4
|
||||
assert sample_data['i8'] or { 0 }.as_array()[0].i8() == 5
|
||||
assert sample_data['i16'] or { 0 }.as_array()[0].i16() == 6
|
||||
assert sample_data['i32'] or { 0 }.as_array()[0].i32() == 7
|
||||
assert sample_data['int'] or { 0 }.as_array()[0].int() == 8
|
||||
assert sample_data['i64'] or { 0 }.as_array()[0].i64() == 9
|
||||
assert sample_data['f32'] or { 0 }.as_array()[0].f32() == 2.3
|
||||
assert sample_data['f64'] or { 0 }.as_array()[0].f64() == 1.283
|
||||
assert sample_data['bool'] or { 0 }.as_array()[0].bool() == false
|
||||
assert sample_data['str'] or { 0 }.as_array()[0].str() == 'test'
|
||||
assert is_null(sample_data['null'] or { 0 }.as_array()[0]) == true
|
||||
assert sample_data['arr'] or { 0 }.as_array()[0].str() == 'lol'
|
||||
assert sample_data['obj'] or { 0 }.as_array()[0].int() == 10
|
||||
}
|
||||
|
||||
fn test_bool() {
|
||||
// valid conversions
|
||||
assert sample_data['bool'] or { 0 }.bool() == false
|
||||
assert json.Any('true').bool() == true
|
||||
assert sample_data['u8'] or { 0 }.bool() == true
|
||||
assert sample_data['u16'] or { 0 }.bool() == true
|
||||
assert sample_data['u32'] or { 0 }.bool() == true
|
||||
assert sample_data['u64'] or { 0 }.bool() == true
|
||||
assert sample_data['i8'] or { 0 }.bool() == true
|
||||
assert sample_data['i16'] or { 0 }.bool() == true
|
||||
assert sample_data['i32'] or { 0 }.bool() == true
|
||||
assert sample_data['int'] or { 0 }.bool() == true
|
||||
assert sample_data['i64'] or { 0 }.bool() == true
|
||||
assert sample_data['f32'] or { 0 }.bool() == true
|
||||
assert sample_data['f64'] or { 0 }.bool() == true
|
||||
// invalid conversions
|
||||
assert sample_data['null'] or { 0 }.bool() == false
|
||||
assert sample_data['arr'] or { 0 }.bool() == false
|
||||
assert sample_data['obj'] or { 0 }.bool() == false
|
||||
}
|
||||
|
||||
fn test_str() {
|
||||
assert sample_data['u8'] or { 0 }.str() == '1'
|
||||
assert sample_data['u16'] or { 0 }.str() == '2'
|
||||
assert sample_data['u32'] or { 0 }.str() == '3'
|
||||
assert sample_data['u64'] or { 0 }.str() == '4'
|
||||
assert sample_data['i8'] or { 0 }.str() == '5'
|
||||
assert sample_data['i16'] or { 0 }.str() == '6'
|
||||
assert sample_data['i32'] or { 0 }.str() == '7'
|
||||
assert sample_data['int'] or { 0 }.str() == '8'
|
||||
assert sample_data['i64'] or { 0 }.str() == '9'
|
||||
assert sample_data['f32'] or { 0 }.str() == '2.3'
|
||||
assert sample_data['f64'] or { 0 }.str() == '1.283'
|
||||
assert sample_data['bool'] or { 0 }.str() == 'false'
|
||||
assert sample_data['str'] or { 0 }.str() == 'test'
|
||||
assert sample_data['null'] or { 0 }.str() == 'null'
|
||||
assert sample_data['arr'] or { 'not lol' }.str() == '["lol"]'
|
||||
assert sample_data.str() == '{"u8":1,"u16":2,"u32":3,"u64":4,"i8":5,"i16":6,"i32":7,"int":8,"i64":9,"f32":2.3,"f64":1.283,"bool":false,"str":"test","null":null,"arr":["lol"],"obj":{"foo":10}}'
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct StruWithJsonAttribute {
|
||||
a int
|
||||
name2 string @[json: 'name']
|
||||
b int
|
||||
}
|
||||
|
||||
struct StruWithSkipAttribute {
|
||||
a int
|
||||
name ?string @[skip]
|
||||
b int
|
||||
}
|
||||
|
||||
struct StruWithJsonSkipAttribute {
|
||||
a int
|
||||
name ?string @[json: '-']
|
||||
b int
|
||||
}
|
||||
|
||||
struct StruWithOmitemptyAttribute {
|
||||
a int
|
||||
name ?string @[omitempty]
|
||||
b int
|
||||
}
|
||||
|
||||
struct StruWithRawAttribute {
|
||||
a int
|
||||
name string @[raw]
|
||||
object string @[raw]
|
||||
b int
|
||||
}
|
||||
|
||||
struct StruWithOptionalRawAttribute {
|
||||
data ?string @[raw]
|
||||
}
|
||||
|
||||
struct StruWithRequiredAttribute {
|
||||
a int
|
||||
name string @[required]
|
||||
skip_and_required ?string @[required; skip]
|
||||
b int
|
||||
}
|
||||
|
||||
struct Foo {
|
||||
a int @[required]
|
||||
}
|
||||
|
||||
struct JsonNullAttrBar {
|
||||
name ?string @[json_null]
|
||||
}
|
||||
|
||||
struct JsonNullAttrFoo {
|
||||
name ?string @[json_null]
|
||||
age ?int @[json_null]
|
||||
text ?string
|
||||
other ?JsonNullAttrBar
|
||||
other2 ?JsonNullAttrBar @[json_null]
|
||||
}
|
||||
|
||||
fn test_last_field_requiered() {
|
||||
assert json.decode[Foo]('{"a":0}')! == Foo{
|
||||
a: 0
|
||||
}
|
||||
}
|
||||
|
||||
fn test_json_null_attribute() {
|
||||
assert json.encode(JsonNullAttrFoo{}) == '{"name":null,"age":null,"other2":null}'
|
||||
assert json.encode(JsonNullAttrFoo{ name: '' }) == '{"name":"","age":null,"other2":null}'
|
||||
assert json.encode(JsonNullAttrFoo{ age: 10 }) == '{"name":null,"age":10,"other2":null}'
|
||||
assert json.encode(JsonNullAttrFoo{
|
||||
age: 10
|
||||
other2: JsonNullAttrBar{
|
||||
name: none
|
||||
}
|
||||
}) == '{"name":null,"age":10,"other2":{"name":null}}'
|
||||
assert json.decode[JsonNullAttrFoo](json.encode(JsonNullAttrFoo{}))! == JsonNullAttrFoo{}
|
||||
}
|
||||
|
||||
fn test_skip_and_rename_attributes() {
|
||||
assert json.decode[StruWithJsonAttribute]('{"name": "hola1", "a": 2, "b": 3}')! == StruWithJsonAttribute{
|
||||
a: 2
|
||||
name2: 'hola1'
|
||||
b: 3
|
||||
}, '`json` attribute not working'
|
||||
|
||||
assert json.decode[StruWithSkipAttribute]('{"name": "hola2", "a": 2, "b": 3}')! == StruWithSkipAttribute{
|
||||
a: 2
|
||||
name: none
|
||||
b: 3
|
||||
}, '`skip` attribute not working'
|
||||
|
||||
assert json.decode[StruWithJsonSkipAttribute]('{"name": "hola3", "a": 2, "b": 3}')! == StruWithJsonSkipAttribute{
|
||||
a: 2
|
||||
name: none
|
||||
b: 3
|
||||
}, " `json: '-'` skip attribute not working"
|
||||
|
||||
assert json.decode[StruWithOmitemptyAttribute]('{"name": "", "a": 2, "b": 3}')! == StruWithOmitemptyAttribute{
|
||||
a: 2
|
||||
name: none
|
||||
b: 3
|
||||
}, '`omitempty` attribute not working'
|
||||
|
||||
assert json.decode[StruWithOmitemptyAttribute]('{"name": "hola", "a": 2, "b": 3}')! == StruWithOmitemptyAttribute{
|
||||
a: 2
|
||||
name: 'hola'
|
||||
b: 3
|
||||
}, '`omitempty` attribute not working'
|
||||
}
|
||||
|
||||
fn test_raw_attribute() {
|
||||
assert json.decode[StruWithRawAttribute]('{"name": "hola", "a": 2, "object": {"c": 4, "d": 5}, "b": 3}')! == StruWithRawAttribute{
|
||||
a: 2
|
||||
name: '"hola"'
|
||||
object: '{"c": 4, "d": 5}'
|
||||
b: 3
|
||||
}, '`raw` attribute not working'
|
||||
}
|
||||
|
||||
fn test_optional_raw_attribute_preserves_source_bytes() {
|
||||
raw_json := '{
|
||||
"data": { "test": 1 }
|
||||
}'
|
||||
dto := json.decode[StruWithOptionalRawAttribute](raw_json)!
|
||||
assert dto.data? == '{ "test": 1 }'
|
||||
}
|
||||
|
||||
fn test_required_attribute() {
|
||||
assert json.decode[StruWithRequiredAttribute]('{"name": "hola", "a": 2, "skip_and_required": "hola", "b": 3}')! == StruWithRequiredAttribute{
|
||||
a: 2
|
||||
name: 'hola'
|
||||
skip_and_required: none
|
||||
b: 3
|
||||
}, '`required` attribute not working'
|
||||
|
||||
mut has_error := false
|
||||
|
||||
json.decode[StruWithRequiredAttribute]('{"name": "hola", "a": 2, "b": 3}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 31
|
||||
assert err.message == 'Data: missing required field `skip_and_required`'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
|
||||
assert has_error, '`required` attribute not working. It should have failed'
|
||||
has_error = false
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// vtest build: !sanitize-memory-gcc && !sanitize-address-gcc && !sanitize-address-clang
|
||||
// vtest vflags: -autofree
|
||||
import x.json2
|
||||
|
||||
struct Config {
|
||||
bbb bool
|
||||
}
|
||||
|
||||
fn test_compilation_with_autofree() {
|
||||
cfg := Config{}
|
||||
s := json2.encode(cfg, prettify: true)
|
||||
assert s == '{\n "bbb": false\n}'
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import x.json2
|
||||
import json as old_json
|
||||
import benchmark
|
||||
import time
|
||||
|
||||
// ./v wipe-cache && ./v -prod -cc gcc crun vlib/x/json2/tests/bench.v
|
||||
const max_iterations = 1_000_000
|
||||
// const max_iterations = 10 // trying figure out it is slower in small loop. I guess it is `fulfill_nodes` related. Any suggestion?
|
||||
|
||||
pub struct Stru {
|
||||
val int
|
||||
val2 string
|
||||
val3 Stru2
|
||||
}
|
||||
|
||||
pub struct Stru2 {
|
||||
a int
|
||||
churrasco string
|
||||
}
|
||||
|
||||
type SumTypes = Stru | bool | int | string | time.Time
|
||||
type StringAlias = string
|
||||
type IntAlias = int
|
||||
|
||||
enum Enum {
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e = 99
|
||||
f
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeOption[T] {
|
||||
mut:
|
||||
val ?T
|
||||
}
|
||||
|
||||
struct StructTypePointer[T] {
|
||||
mut:
|
||||
val &T
|
||||
}
|
||||
|
||||
fn main() {
|
||||
json_data := '{"_type": "Stru", "val": 1, "val2": "lala", "val3": {"a": 2, "churrasco": "leleu"}}'
|
||||
json_data1 := '{"val": "2"}'
|
||||
json_data2 := '{"val": 2}'
|
||||
|
||||
println('Starting benchmark...')
|
||||
println('max_iterations: ${max_iterations}')
|
||||
println('\n***Structure and maps***')
|
||||
|
||||
mut b := benchmark.start()
|
||||
|
||||
// Stru **********************************************************
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[Stru](json_data)!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[Stru](json_data)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode(Stru, json_data)!
|
||||
}
|
||||
|
||||
b.measure('old_json.decode(Stru, json_data)!\n')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[SumTypes](json_data)!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[SumTypes](json_data)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode(SumTypes, json_data)!
|
||||
}
|
||||
|
||||
b.measure('old_json.decode(SumTypes, json_data)!\n')
|
||||
|
||||
// StructType[string] **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[StructType[string]](json_data1)!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[StructType[string]](json_data1)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode(StructType[string], json_data1)!
|
||||
}
|
||||
|
||||
b.measure('old_json.decode(StructType[string], json_data1)!\n')
|
||||
|
||||
// StructTypeOption[string] **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[StructTypeOption[string]](json_data1)!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[StructTypeOption[string]](json_data1)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode(StructTypeOption[string], json_data1)!
|
||||
}
|
||||
|
||||
b.measure('old_json.decode(StructTypeOption[string], json_data1)!\n')
|
||||
|
||||
// StructType[int] **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[StructType[int]](json_data2)!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[StructType[int]](json_data2)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode(StructType[int], json_data2)!
|
||||
}
|
||||
|
||||
b.measure('old_json.decode(StructType[int], json_data2)!\n')
|
||||
|
||||
// map[string]string **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[map[string]string](json_data1)!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[map[string]string](json_data1)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode(map[string]string, json_data1)!
|
||||
}
|
||||
|
||||
b.measure('old_json.decode(map[string]string, json_data1)!\n')
|
||||
|
||||
// array **********************************************************
|
||||
|
||||
println('\n***arrays***')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[[]int]('[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]')!
|
||||
}
|
||||
|
||||
b.measure("json2.decode[[]int]('[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]')!")
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := old_json.decode([]int, '[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]')!
|
||||
}
|
||||
|
||||
b.measure("old_json.decode([]int, '[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]')!\n")
|
||||
|
||||
println('\n***simple types***')
|
||||
|
||||
// int **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[int]('2')!
|
||||
}
|
||||
|
||||
b.measure("json2.decode[int]('2')!")
|
||||
|
||||
// bool **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[bool]('true')!
|
||||
}
|
||||
|
||||
b.measure("json2.decode[bool]('true')!")
|
||||
|
||||
// time.Time **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[time.Time]('"2022-03-11T13:54:25"')!
|
||||
}
|
||||
|
||||
b.measure("json2.decode[time.Time]('2022-03-11T13:54:25')!")
|
||||
|
||||
// string **********************************************************
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[string]('"abcdefghijklimnopqrstuv"')!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[string](\'"abcdefghijklimnopqrstuv"\')!')
|
||||
|
||||
// alias **********************************************************
|
||||
|
||||
println('\n***alias***')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[IntAlias]('2')!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[IntAlias](2)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[StringAlias]('"abcdefghijklimnopqrstuv"')!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[StringAlias](\'"abcdefghijklimnopqrstuv"\')!')
|
||||
|
||||
println('\n***Sumtypes***')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[SumTypes]('2')!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[SumTypes](2)!')
|
||||
|
||||
for i := 0; i < max_iterations; i++ {
|
||||
_ := json2.decode[SumTypes]('"abcdefghijklimnopqrstuv"')!
|
||||
}
|
||||
|
||||
b.measure('json2.decode[SumTypes](\'"abcdefghijklimnopqrstuv"\')!')
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import x.json2 as json
|
||||
|
||||
fn test_check_if_json_match() {
|
||||
// /* Test wrong string values */
|
||||
mut has_error := false
|
||||
|
||||
json.decode[string]('{"key": "value"}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected string, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
json.decode[map[string]string]('"value"') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected object, but got string'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
json.decode[[]int]('{"key": "value"}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected array, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
json.decode[string]('[1, 2, 3]') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected string, but got array'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
json.decode[int]('{"key": "value"}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected number, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
json.decode[bool]('{"key": "value"}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected boolean, but got object'
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
// /* Right string values */
|
||||
json.decode[string]('"value"') or { assert false }
|
||||
|
||||
json.decode[map[string]string]('{"key": "value"}') or { assert false }
|
||||
|
||||
json.decode[[]int]('[1, 2, 3]') or { assert false }
|
||||
|
||||
json.decode[string]('"string"') or { assert false }
|
||||
|
||||
json.decode[int]('123') or { assert false }
|
||||
|
||||
json.decode[bool]('true') or { assert false }
|
||||
|
||||
json.decode[bool]('false') or { assert false }
|
||||
}
|
||||
|
||||
fn test_check_json_format() {
|
||||
json_and_error_message := [
|
||||
{
|
||||
'json': ']'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': '}'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': 'truely'
|
||||
'error': 'Syntax: invalid value. Unexpected character after boolean end'
|
||||
},
|
||||
{
|
||||
'json': '0[1]'
|
||||
'error': 'Syntax: invalid value. Unexpected character after number end'
|
||||
},
|
||||
{
|
||||
'json': '[1, 2, g3]'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': '[1, 2,, 3]'
|
||||
'error': 'Syntax: unknown value kind'
|
||||
},
|
||||
{
|
||||
'json': '{"key": 123'
|
||||
'error': 'Syntax: Expecting object key' // improve message
|
||||
},
|
||||
{
|
||||
'json': '{"key": 123,'
|
||||
'error': 'Syntax: EOF: expected object key'
|
||||
},
|
||||
{
|
||||
'json': '{"key": 123, "key2": 456,}'
|
||||
'error': 'Syntax: Cannot use `,`, before `}`'
|
||||
},
|
||||
{
|
||||
'json': '[[1, 2, 3], [4, 5, 6],]'
|
||||
'error': 'Syntax: Cannot use `,`, before `]`'
|
||||
},
|
||||
{
|
||||
'json': ' '
|
||||
'error': 'Syntax: EOF: empty json'
|
||||
},
|
||||
{
|
||||
'json': '"'
|
||||
'error': 'Syntax: EOF: string not closed'
|
||||
},
|
||||
{
|
||||
'json': '"not closed'
|
||||
'error': 'Syntax: EOF: string not closed'
|
||||
},
|
||||
{
|
||||
'json': '"\\"'
|
||||
'error': 'Syntax: EOF: string not closed'
|
||||
},
|
||||
{
|
||||
'json': '"\\u8"'
|
||||
'error': 'Syntax: short unicode escape sequence \\u8'
|
||||
},
|
||||
{
|
||||
'json': '['
|
||||
'error': 'Syntax: EOF: expected array end'
|
||||
},
|
||||
{
|
||||
'json': '[ '
|
||||
'error': 'Syntax: EOF: expected array end'
|
||||
},
|
||||
{
|
||||
'json': '{'
|
||||
'error': 'Syntax: EOF: expected object end'
|
||||
},
|
||||
{
|
||||
'json': '{ '
|
||||
'error': 'Syntax: EOF: expected object end'
|
||||
},
|
||||
{
|
||||
'json': '{"key": "value" '
|
||||
'error': 'Syntax: EOF: expected object end'
|
||||
},
|
||||
]
|
||||
|
||||
for json_and_error in json_and_error_message {
|
||||
mut has_error := false
|
||||
|
||||
json.decode[json.Any](json_and_error['json']) or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.message == json_and_error['error']
|
||||
}
|
||||
has_error = true
|
||||
}
|
||||
assert has_error, 'Expected error ${json_and_error['error']}'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct AnyStruct[T] {
|
||||
val T
|
||||
}
|
||||
|
||||
struct OptAnyStruct[T] {
|
||||
val ?T
|
||||
}
|
||||
|
||||
/*struct OptAnyArrStruct {
|
||||
val []?json.Any
|
||||
}*/
|
||||
|
||||
fn test_values() {
|
||||
assert json.decode[AnyStruct[json.Any]]('{"val":5}')!.val.int() == 5
|
||||
assert json.decode[OptAnyStruct[json.Any]]('{}')!.val == none
|
||||
assert json.decode[AnyStruct[[]json.Any]]('{"val":[5,10]}')!.val.map(it.int()) == [
|
||||
5,
|
||||
10,
|
||||
]
|
||||
// assert json.decode[OptAnyArrStruct]('{"val":[5,null,10]}')!.val == [?json.Any(5),json.Null{},10] skipped because test still fails even though they're the same
|
||||
|
||||
assert json.encode[AnyStruct[json.Any]](AnyStruct[json.Any]{json.Any(5)}) == '{"val":5}'
|
||||
assert json.encode[OptAnyStruct[json.Any]](OptAnyStruct[json.Any]{none}) == '{}'
|
||||
assert json.encode[AnyStruct[[]json.Any]](AnyStruct[[]json.Any]{[json.Any(5), 10]}) == '{"val":[5,10]}'
|
||||
// assert json.encode[OptAnyArrStruct](OptAnyArrStruct{[?json.Any(5),none,10]}) == '{"val":[5,null,10]}' encode_array has not implemented optional arrays yet
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import x.json2 as json
|
||||
|
||||
type Issue24127Any = json.Any
|
||||
type Issue24127Map = map[string]Issue24127Any
|
||||
|
||||
const issue_24127_json_inputs = [
|
||||
'{}',
|
||||
'{"a":"b"}',
|
||||
'{"a":1}',
|
||||
'{"a":3.14}',
|
||||
'{"a":{"b":"c"}}',
|
||||
'{"a":true}',
|
||||
]
|
||||
|
||||
fn test_decode_alias_of_any_sumtype() {
|
||||
for input in issue_24127_json_inputs {
|
||||
direct := json.decode[json.Any](input)!
|
||||
aliased := json.decode[Issue24127Any](input)!
|
||||
assert json.encode(direct) == json.encode(aliased)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_decode_map_with_any_alias_value() {
|
||||
for input in issue_24127_json_inputs {
|
||||
direct := json.decode[map[string]json.Any](input)!
|
||||
aliased := json.decode[Issue24127Map](input)!
|
||||
assert json.encode(direct) == json.encode(aliased)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
module main
|
||||
|
||||
import json
|
||||
import x.json2
|
||||
|
||||
pub struct Data {
|
||||
name string
|
||||
data [][]f64
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
json_data := '{"name":"test","data":[[1,2,3],[4,5,6]]}'
|
||||
info := json.decode(Data, json_data)!
|
||||
info2 := json2.decode[Data](json_data)!
|
||||
assert info == info2
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
fn test_array_of_strings() {
|
||||
assert json.decode[[]int]('[1, 2, 3]')! == [1, 2, 3]
|
||||
|
||||
assert json.decode[[]string]('["a", "b", "c"]')! == ['a', 'b', 'c']
|
||||
|
||||
assert json.decode[[]bool]('[true, false, true]')! == [true, false, true]
|
||||
|
||||
assert json.decode[[]f64]('[1.1, 2.2, 3.3]')! == [1.1, 2.2, 3.3]
|
||||
|
||||
// nested arrays
|
||||
assert json.decode[[][]int]('[[1, 22], [333, 4444]]')! == [
|
||||
[1, 22],
|
||||
[333, 4444],
|
||||
]
|
||||
|
||||
assert json.decode[[][]string]('[["a", "b"], ["c", "d"]]')! == [
|
||||
['a', 'b'],
|
||||
['c', 'd'],
|
||||
]
|
||||
|
||||
assert json.decode[[][]bool]('[[true, false], [false, true]]')! == [
|
||||
[true, false],
|
||||
[false, true],
|
||||
]
|
||||
}
|
||||
|
||||
fn test_array_of_struct() {
|
||||
assert json.decode[[]StructType[int]]('[{"val": 1}, {"val": 2}, {"val": 3}, {"val": 4}]')! == [
|
||||
StructType{
|
||||
val: 1
|
||||
},
|
||||
StructType{
|
||||
val: 2
|
||||
},
|
||||
StructType{
|
||||
val: 3
|
||||
},
|
||||
StructType{
|
||||
val: 4
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import x.json2 as json
|
||||
|
||||
// Tests for strict mode: quoted strings are NOT decoded as numbers (JSON spec compliance).
|
||||
// Tests for default mode: quoted strings ARE accepted as numbers for compatibility.
|
||||
|
||||
// ===== STRICT MODE TESTS =====
|
||||
|
||||
fn test_strict_string_not_decoded_as_int() {
|
||||
json.decode[int]('"0"', strict: true) or {
|
||||
assert err is json.JsonDecodeError
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.message == 'Data: Expected number, but got string'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false, 'Expected JsonDecodeError for quoted number string in strict mode'
|
||||
}
|
||||
|
||||
fn test_strict_string_not_decoded_as_int_positive() {
|
||||
json.decode[int]('"100"', strict: true) or {
|
||||
assert err is json.JsonDecodeError
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.message == 'Data: Expected number, but got string'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false, 'Expected JsonDecodeError for quoted number string in strict mode'
|
||||
}
|
||||
|
||||
fn test_strict_string_not_decoded_as_float() {
|
||||
json.decode[f64]('"-23.6e1"', strict: true) or {
|
||||
assert err is json.JsonDecodeError
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.message == 'Data: Expected number, but got string'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false, 'Expected JsonDecodeError for quoted number string in strict mode'
|
||||
}
|
||||
|
||||
fn test_strict_string_in_array_not_decoded_as_int() {
|
||||
json.decode[[]int]('["100", 99, "98", 97]', strict: true) or {
|
||||
assert err is json.JsonDecodeError
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.message == 'Data: Expected number, but got string'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false, 'Expected JsonDecodeError for quoted number string in array in strict mode'
|
||||
}
|
||||
|
||||
// ===== DEFAULT MODE TESTS =====
|
||||
|
||||
fn test_default_string_decoded_as_int() {
|
||||
assert json.decode[int]('"0"')! == 0
|
||||
assert json.decode[int]('"100"')! == 100
|
||||
assert json.decode[int]('"-50"')! == -50
|
||||
}
|
||||
|
||||
fn test_default_string_decoded_as_float() {
|
||||
assert json.decode[f64]('"-23.6e1"')! == -236.0
|
||||
assert json.decode[f64]('"3.14"')! == 3.14
|
||||
}
|
||||
|
||||
fn test_default_string_in_array_decoded_as_int() {
|
||||
assert json.decode[[]int]('["100", "99", "98", "97"]')! == [100, 99, 98, 97]
|
||||
assert json.decode[[]int]('["100", 99, "98", 97]')! == [100, 99, 98, 97]
|
||||
}
|
||||
|
||||
// ===== COMMON TESTS (both modes) =====
|
||||
|
||||
fn test_valid_unquoted_numbers() {
|
||||
assert json.decode[int]('0')! == 0
|
||||
assert json.decode[int]('100')! == 100
|
||||
assert json.decode[int]('-50')! == -50
|
||||
assert json.decode[f64]('-23.6e1')! == -236.0
|
||||
assert json.decode[[]int]('[100, 99, 98, 97]')! == [100, 99, 98, 97]
|
||||
|
||||
// Strict mode also works for unquoted numbers
|
||||
assert json.decode[int]('0', strict: true)! == 0
|
||||
assert json.decode[int]('100', strict: true)! == 100
|
||||
}
|
||||
|
||||
fn test_invalid_number_string_fails() {
|
||||
json.decode[int]('"not_a_number"') or { return }
|
||||
assert false, 'Expected error for invalid number string'
|
||||
}
|
||||
|
||||
fn test_leading_plus_in_string() {
|
||||
assert json.decode[int]('"+100"')! == 100
|
||||
|
||||
json.decode[int]('"+100"', strict: true) or {
|
||||
assert err is json.JsonDecodeError
|
||||
return
|
||||
}
|
||||
assert false, 'Expected error in strict mode'
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import x.json2 as json
|
||||
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
|
||||
}
|
||||
|
||||
type MyString2 = string
|
||||
|
||||
pub fn (mut ms MyString2) from_json_string(raw_string string) ! {
|
||||
ms = raw_string.replace('-', '')
|
||||
}
|
||||
|
||||
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[json.Any]('null]')! == json.Any(json.null)
|
||||
assert json.decode[json.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'
|
||||
}
|
||||
|
||||
fn test_alias() {
|
||||
assert json.decode[MyString2]('"h-e---l-lo"')! == 'hello'
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import json as stdjson
|
||||
import x.json2
|
||||
|
||||
struct Issue24950GateRecord {
|
||||
exit string
|
||||
}
|
||||
|
||||
struct Issue24950RecordBase {
|
||||
gate &Issue24950GateRecord = unsafe { nil } @[omitempty]
|
||||
}
|
||||
|
||||
struct Issue24950Record {
|
||||
Issue24950RecordBase
|
||||
}
|
||||
|
||||
struct Issue24950Wrapper {
|
||||
gate &Issue24950GateRecord = unsafe { nil }
|
||||
}
|
||||
|
||||
struct Issue25614Node {
|
||||
mut:
|
||||
value int
|
||||
children []&Issue25614Node
|
||||
}
|
||||
|
||||
struct OptionalPointerRegressionMessage {
|
||||
mut:
|
||||
id int
|
||||
text string
|
||||
reply_to ?&OptionalPointerRegressionMessage
|
||||
}
|
||||
|
||||
fn test_decode_struct_with_reference_field() {
|
||||
decoded := json2.decode[Issue24950Wrapper]('{"gate":{"exit":"north"}}')!
|
||||
|
||||
assert decoded.gate != unsafe { nil }
|
||||
assert decoded.gate.exit == 'north'
|
||||
}
|
||||
|
||||
fn test_decode_embedded_struct_with_reference_field() {
|
||||
empty_std := stdjson.decode(Issue24950Record, '{}')!
|
||||
empty_json2 := json2.decode[Issue24950Record]('{}')!
|
||||
|
||||
assert empty_std.Issue24950RecordBase.gate == unsafe { nil }
|
||||
assert empty_json2.Issue24950RecordBase.gate == unsafe { nil }
|
||||
|
||||
populated_std := stdjson.decode(Issue24950Record, '{"gate":{"exit":"north"}}')!
|
||||
populated_json2 := json2.decode[Issue24950Record]('{"gate":{"exit":"north"}}')!
|
||||
|
||||
assert populated_std.Issue24950RecordBase.gate != unsafe { nil }
|
||||
assert populated_json2.Issue24950RecordBase.gate != unsafe { nil }
|
||||
assert populated_std.Issue24950RecordBase.gate.exit == 'north'
|
||||
assert populated_json2.Issue24950RecordBase.gate.exit == 'north'
|
||||
}
|
||||
|
||||
fn test_decode_top_level_reference_to_primitive() {
|
||||
decoded := json2.decode[&int]('1')!
|
||||
|
||||
assert decoded != unsafe { nil }
|
||||
assert *decoded == 1
|
||||
}
|
||||
|
||||
fn test_decode_top_level_reference_to_primitive_null() {
|
||||
decoded := json2.decode[&int]('null')!
|
||||
|
||||
assert decoded == unsafe { nil }
|
||||
}
|
||||
|
||||
fn test_decode_top_level_reference_to_struct() {
|
||||
decoded := json2.decode[&Issue25614Node]('{"value":1}')!
|
||||
|
||||
assert decoded != unsafe { nil }
|
||||
assert decoded.value == 1
|
||||
}
|
||||
|
||||
fn test_decode_struct_with_array_of_references() {
|
||||
decoded := json2.decode[Issue25614Node]('{"children":[{"value":1},{"value":2}]}')!
|
||||
|
||||
assert decoded.children.len == 2
|
||||
assert decoded.children[0] != unsafe { nil }
|
||||
assert decoded.children[1] != unsafe { nil }
|
||||
assert decoded.children[0].value == 1
|
||||
assert decoded.children[1].value == 2
|
||||
}
|
||||
|
||||
fn test_decode_optional_reference_field() {
|
||||
message :=
|
||||
json2.decode[OptionalPointerRegressionMessage]('{"id":1,"text":"Hello","reply_to":{"id":2,"text":"Hi","reply_to":null}}')!
|
||||
|
||||
reply := message.reply_to or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
|
||||
assert reply.id == 2
|
||||
assert reply.text == 'Hi'
|
||||
assert reply.reply_to == none
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import x.json2 as json
|
||||
|
||||
enum Bar {
|
||||
a
|
||||
b @[json: 'BBB']
|
||||
c = 10
|
||||
e @[badattr: 'bad'; foobar]
|
||||
}
|
||||
|
||||
type BarAlias = Bar
|
||||
|
||||
fn test_number_decode() {
|
||||
assert json.decode[Bar]('0')! == Bar.a
|
||||
assert json.decode[Bar]('1')! == Bar.b
|
||||
assert json.decode[Bar]('10')! == Bar.c
|
||||
|
||||
assert json.decode[BarAlias]('0')! == Bar.a
|
||||
assert json.decode[BarAlias]('1')! == Bar.b
|
||||
assert json.decode[BarAlias]('10')! == Bar.c
|
||||
}
|
||||
|
||||
fn test_number_decode_fails() {
|
||||
if _ := json.decode[Bar]('2') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Number value: `2` does not match any field in enum: &Bar'
|
||||
}
|
||||
}
|
||||
|
||||
if _ := json.decode[BarAlias]('2') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Number value: `2` does not match any field in enum: &BarAlias'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_string_decode() {
|
||||
assert json.decode[Bar]('"a"')! == Bar.a
|
||||
assert json.decode[Bar]('"BBB"')! == Bar.b
|
||||
assert json.decode[Bar]('"c"')! == Bar.c
|
||||
|
||||
assert json.decode[BarAlias]('"a"')! == Bar.a
|
||||
assert json.decode[BarAlias]('"BBB"')! == Bar.b
|
||||
assert json.decode[BarAlias]('"c"')! == Bar.c
|
||||
}
|
||||
|
||||
fn test_string_decode_uses_only_json_attr() {
|
||||
assert json.decode[Bar]('"e"')! == Bar.e
|
||||
assert json.decode[BarAlias]('"e"')! == Bar.e
|
||||
for badval in ['"badattr: bad"', '"bad"', '"foobar"'] {
|
||||
if _ := json.decode[Bar](badval) {
|
||||
assert false, '${badval} must not be decoded to Bar'
|
||||
} else {
|
||||
assert true
|
||||
}
|
||||
if _ := json.decode[BarAlias](badval) {
|
||||
assert false, '${badval} must not be decoded to BarAlias'
|
||||
} else {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_string_decode_fails() {
|
||||
if _ := json.decode[Bar]('"d"') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: String value: `d` does not match any field in enum: &Bar'
|
||||
}
|
||||
}
|
||||
|
||||
if _ := json.decode[BarAlias]('"d"') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: String value: `d` does not match any field in enum: &BarAlias'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_invalid_decode_fails() {
|
||||
if _ := json.decode[Bar]('true') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected number or string value for enum, got: boolean'
|
||||
}
|
||||
}
|
||||
|
||||
if _ := json.decode[BarAlias]('true') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected number or string value for enum, got: boolean'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_map_string_enum_decode() {
|
||||
m := json.decode[map[string]Bar]('{"foo": "a", "bar": "b", "baz": "c"}')!
|
||||
assert m['foo'] == .a
|
||||
assert m['bar'] == .b
|
||||
assert m['baz'] == .c
|
||||
|
||||
m2 := json.decode[map[string]Bar]('{"x": 0, "y": 1, "z": 10}')!
|
||||
assert m2['x'] == .a
|
||||
assert m2['y'] == .b
|
||||
assert m2['z'] == .c
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import x.json2
|
||||
|
||||
fn test_decode_escaped_string() {
|
||||
escaped_strings := ['test', 'test\\sd', 'test\nsd', '\ntest', 'test\\"', 'test\\', 'test\u1234ps',
|
||||
'test\u1234', '\u1234\\\t"', '', '\uff0f', 'test \uff0f test', '😀', 'text 😀 text']
|
||||
|
||||
json_string := json2.encode[[]string](escaped_strings)
|
||||
decoded_strings := json2.decode[[]string](json_string)!
|
||||
|
||||
assert escaped_strings == decoded_strings
|
||||
}
|
||||
|
||||
fn test_surrogate() {
|
||||
assert json2.decode[string](r'"\ud83d\ude00"')! == '😀'
|
||||
assert json2.decode[string](r'"\ud83d\ude00 text"')! == '😀 text'
|
||||
}
|
||||
|
||||
fn test_invalid_surrogate() {
|
||||
if x := json2.decode[string](r'"\ud83d"') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json2.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected a trail surrogate after a head surrogate, but got no valid escape sequence.'
|
||||
}
|
||||
}
|
||||
|
||||
if x := json2.decode[string](r'"\ud83d\n\n\n\n"') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json2.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected a trail surrogate after a head surrogate, but got no valid escape sequence.'
|
||||
}
|
||||
}
|
||||
|
||||
if x := json2.decode[string](r'"\ud83d\ud83d"') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json2.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Expected a trail surrogate after a head surrogate, but got D83D.'
|
||||
}
|
||||
}
|
||||
|
||||
if x := json2.decode[string](r'"\ude00\ud83d"') {
|
||||
assert false
|
||||
} else {
|
||||
if err is json2.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Data: Got trail surrogate: DE00 before head surrogate.'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import x.json2 as json
|
||||
|
||||
fn test_fixed_array() {
|
||||
mut expected := [3]int{}
|
||||
expected[0] = 1
|
||||
expected[1] = 2
|
||||
expected[2] = 3
|
||||
assert json.decode[[3]int]('[1, 2, 3]')! == expected
|
||||
}
|
||||
|
||||
fn test_fixed_array_to_few() {
|
||||
json.decode[[4]int]('[1, 2, 3]') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 8
|
||||
assert err.message == 'Data: Fixed size array expected 4 elements but got 3 elements'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_fixed_array_to_many() {
|
||||
json.decode[[2]int]('[1, 2, 3]') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 8
|
||||
assert err.message == 'Data: Fixed size array expected 2 elements but got 3 elements'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import json as old_json
|
||||
import x.json2 as json
|
||||
|
||||
struct Legacy64BitNumbers {
|
||||
signed i64
|
||||
unsigned u64
|
||||
}
|
||||
|
||||
fn test_decode_float_numbers_into_64bit_integer_fields_matches_json_module() {
|
||||
payload := '{"signed": -1756811041916.0, "unsigned": 1756811041916.0}'
|
||||
expected := old_json.decode(Legacy64BitNumbers, payload)!
|
||||
actual := json.decode[Legacy64BitNumbers](payload)!
|
||||
assert actual == expected
|
||||
}
|
||||
|
||||
fn test_decode_float_strings_into_64bit_integer_fields_in_default_mode() {
|
||||
payload := '{"signed": "-1756811041916.0", "unsigned": "1756811041916.0"}'
|
||||
actual := json.decode[Legacy64BitNumbers](payload)!
|
||||
assert actual.signed == i64(-1756811041916)
|
||||
assert actual.unsigned == u64(1756811041916)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import x.json2
|
||||
|
||||
fn decode_any_issue_24128(j string) !json2.Any {
|
||||
return json2.decode[json2.Any](j)
|
||||
}
|
||||
|
||||
fn decode_map_issue_24128(j string) !map[string]json2.Any {
|
||||
return json2.decode[map[string]json2.Any](j)
|
||||
}
|
||||
|
||||
fn test_decode_any_and_map_any_in_same_program() {
|
||||
for payload in [
|
||||
'{}',
|
||||
'{"a":"b"}',
|
||||
'{"a":1}',
|
||||
'{"a":3.14}',
|
||||
'{"a":{"b":"c"}}',
|
||||
'{"a":true}',
|
||||
] {
|
||||
decoded_any := decode_any_issue_24128(payload)!
|
||||
decoded_map := decode_map_issue_24128(payload)!
|
||||
|
||||
match decoded_any {
|
||||
map[string]json2.Any {
|
||||
assert decoded_any.str() == decoded_map.str()
|
||||
}
|
||||
else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_decode_map_any_keeps_numeric_values() {
|
||||
payload := {
|
||||
'sub': json2.Any('453636')
|
||||
'aud': json2.Any('Coachonko')
|
||||
'iss': json2.Any('Coachonko')
|
||||
'nbf': json2.Any(1688229809)
|
||||
'iat': json2.Any(1688229809)
|
||||
'jti': json2.Any('e0a26a81-9cc0-4f52-b672-4f6f3d3b44a5')
|
||||
'exp': json2.Any(1688273009)
|
||||
}
|
||||
encoded := json2.encode(payload)
|
||||
decoded := json2.decode[map[string]json2.Any](encoded)!
|
||||
|
||||
assert decoded.len == payload.len
|
||||
assert 'sub' in decoded
|
||||
assert 'aud' in decoded
|
||||
assert 'iss' in decoded
|
||||
assert 'nbf' in decoded
|
||||
assert 'iat' in decoded
|
||||
assert 'jti' in decoded
|
||||
assert 'exp' in decoded
|
||||
|
||||
assert decoded['sub']!.str() == '453636'
|
||||
assert decoded['aud']!.str() == 'Coachonko'
|
||||
assert decoded['iss']!.str() == 'Coachonko'
|
||||
assert decoded['jti']!.str() == 'e0a26a81-9cc0-4f52-b672-4f6f3d3b44a5'
|
||||
assert decoded['nbf']!.f64() == f64(1688229809)
|
||||
assert decoded['iat']!.f64() == f64(1688229809)
|
||||
assert decoded['exp']!.f64() == f64(1688273009)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import x.json2
|
||||
|
||||
struct CrossVerifyResult {
|
||||
confusion_matrix_map map[string]map[string]f64
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
x := json2.decode[CrossVerifyResult]('') or {
|
||||
assert err.msg().contains('1:1: Invalid json: empty string')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import x.json2
|
||||
|
||||
const data = '
|
||||
{
|
||||
"comments": {
|
||||
"26788945": {
|
||||
"id": "26788945",
|
||||
"message": "some comment 1"
|
||||
},
|
||||
"26788946": {
|
||||
"id": "26788946",
|
||||
"message": "some comment 2"
|
||||
},
|
||||
"26788947": {
|
||||
"id": "26788947",
|
||||
"message": "some comment 3"
|
||||
}
|
||||
},
|
||||
"comments2": {
|
||||
"26788945": true,
|
||||
"26788946": false,
|
||||
"26788947": true
|
||||
},
|
||||
"comments3": {
|
||||
"26788945": 1,
|
||||
"26788946": 2,
|
||||
"26788947": 3
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
pub struct Comment {
|
||||
id string
|
||||
message string
|
||||
}
|
||||
|
||||
struct Comments {
|
||||
mut:
|
||||
comments map[string]Comment
|
||||
comments2 map[string]bool
|
||||
comments3 map[string]int
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
mut root := json2.decode[Comments](data)!
|
||||
assert root.comments.len == 3
|
||||
assert root.comments['26788945']!.id == '26788945'
|
||||
assert root.comments['26788946']!.id == '26788946'
|
||||
assert root.comments['26788947']!.id == '26788947'
|
||||
|
||||
assert root.comments2['26788945']! == true
|
||||
assert root.comments2['26788946']! == false
|
||||
assert root.comments2['26788947']! == true
|
||||
|
||||
assert root.comments3['26788945']! == 1
|
||||
assert root.comments3['26788946']! == 2
|
||||
assert root.comments3['26788947']! == 3
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import x.json2
|
||||
|
||||
struct Dataset {
|
||||
values [][]string
|
||||
}
|
||||
|
||||
struct Dataset2 {
|
||||
values [][]u8
|
||||
}
|
||||
|
||||
fn test_string() {
|
||||
d := Dataset{
|
||||
values: [
|
||||
['a', 'b', 'c'],
|
||||
['d', 'e', 'f'],
|
||||
]
|
||||
}
|
||||
s := json2.encode(d)
|
||||
println(s)
|
||||
d2 := json2.decode[Dataset](s)!
|
||||
assert d2.str() == "Dataset{
|
||||
values: [['a', 'b', 'c'], ['d', 'e', 'f']]
|
||||
}"
|
||||
}
|
||||
|
||||
fn test_u8() {
|
||||
d := Dataset2{
|
||||
values: [
|
||||
[u8(1), 2, 3, 4, 5],
|
||||
[u8(2), 3, 4, 5, 6],
|
||||
]
|
||||
}
|
||||
s := json2.encode(d)
|
||||
println(s)
|
||||
d2 := json2.decode[Dataset2](s)!
|
||||
assert d2.str() == 'Dataset2{
|
||||
values: [[1, 2, 3, 4, 5], [2, 3, 4, 5, 6]]
|
||||
}'
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import x.json2
|
||||
|
||||
// Test for issue #26503: Decoder incorrectly reads nested object fields
|
||||
// When a required field appears after unmatched fields with nested objects/arrays,
|
||||
// the decoder was incorrectly picking up values from nested structures.
|
||||
|
||||
struct Foo {
|
||||
id string @[required]
|
||||
title string @[required]
|
||||
}
|
||||
|
||||
fn test_decode_with_nested_objects_field_order() {
|
||||
// Test case 1: required fields appear before the nested array
|
||||
s1 := '{"id":"sss","title":"ttt","thumb":[{ "url":"i1.jpg","id":"000"}]}'
|
||||
f1 := json2.decode[Foo](s1)!
|
||||
|
||||
assert f1.id == 'sss', 'f1.id should be "sss" but got "${f1.id}"'
|
||||
assert f1.title == 'ttt', 'f1.title should be "ttt" but got "${f1.title}"'
|
||||
|
||||
// Test case 2: required fields appear after the nested array
|
||||
s2 := '{"title":"ttt","thumb":[{ "url":"i1.jpg","id":"000"}],"id":"sss"}'
|
||||
f2 := json2.decode[Foo](s2)!
|
||||
|
||||
assert f2.id == 'sss', 'f2.id should be "sss" but got "${f2.id}"'
|
||||
assert f2.title == 'ttt', 'f2.title should be "ttt" but got "${f2.title}"'
|
||||
|
||||
// Test case 3: nested array appears between required fields
|
||||
s3 := '{"id":"sss","thumb":[{ "url":"i1.jpg","id":"000"}],"title":"ttt"}'
|
||||
f3 := json2.decode[Foo](s3)!
|
||||
|
||||
assert f3.id == 'sss', 'f3.id should be "sss" but got "${f3.id}"'
|
||||
assert f3.title == 'ttt', 'f3.title should be "ttt" but got "${f3.title}"'
|
||||
}
|
||||
|
||||
fn test_decode_with_deeply_nested_objects() {
|
||||
// Test with deeply nested structures to ensure complete skipping
|
||||
s := '{"id":"outer","data":{"nested":{"deep":{"id":"inner","title":"inner_title"}}},"title":"outer_title"}'
|
||||
f := json2.decode[Foo](s)!
|
||||
|
||||
assert f.id == 'outer', 'id should be "outer" but got "${f.id}"'
|
||||
assert f.title == 'outer_title', 'title should be "outer_title" but got "${f.title}"'
|
||||
}
|
||||
|
||||
fn test_decode_with_multiple_nested_arrays() {
|
||||
// Test with multiple nested arrays to ensure complete skipping
|
||||
s := '{"id":"correct","items":[{"id":"a"},{"id":"b"}],"more":[{"id":"c"}],"title":"correct_title"}'
|
||||
f := json2.decode[Foo](s)!
|
||||
|
||||
assert f.id == 'correct', 'id should be "correct" but got "${f.id}"'
|
||||
assert f.title == 'correct_title', 'title should be "correct_title" but got "${f.title}"'
|
||||
}
|
||||
|
||||
struct BarWithOptional {
|
||||
id string @[required]
|
||||
title ?string
|
||||
extra string
|
||||
}
|
||||
|
||||
fn test_decode_optional_fields_with_nested() {
|
||||
// Test optional fields work correctly with nested structures
|
||||
s := '{"id":"main","nested":{"title":"nested_title"},"extra":"extra_value"}'
|
||||
b := json2.decode[BarWithOptional](s)!
|
||||
|
||||
assert b.id == 'main', 'id should be "main" but got "${b.id}"'
|
||||
assert b.extra == 'extra_value', 'extra should be "extra_value" but got "${b.extra}"'
|
||||
if title := b.title {
|
||||
assert false, 'title should be none but got "${title}"'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import x.json2 as json
|
||||
|
||||
fn test_number() {
|
||||
// Test u8
|
||||
assert json.decode[u8]('0')! == 0
|
||||
assert json.decode[u8]('1')! == 1
|
||||
assert json.decode[u8]('201')! == 201
|
||||
|
||||
// Test u16
|
||||
assert json.decode[u16]('0')! == 0
|
||||
assert json.decode[u16]('1')! == 1
|
||||
assert json.decode[u16]('201')! == 201
|
||||
|
||||
// Test u32
|
||||
assert json.decode[u32]('0')! == 0
|
||||
assert json.decode[u32]('1')! == 1
|
||||
assert json.decode[u32]('201')! == 201
|
||||
|
||||
// Test u64
|
||||
assert json.decode[u64]('0')! == 0
|
||||
assert json.decode[u64]('1')! == 1
|
||||
assert json.decode[u64]('201')! == 201
|
||||
|
||||
// Test i8
|
||||
assert json.decode[i8]('0')! == 0
|
||||
assert json.decode[i8]('1')! == 1
|
||||
assert json.decode[i8]('127')! == 127
|
||||
|
||||
assert json.decode[i8]('-1')! == -1
|
||||
assert json.decode[i8]('-127')! == -127
|
||||
|
||||
// Test i16
|
||||
assert json.decode[i16]('0')! == 0
|
||||
assert json.decode[i16]('1')! == 1
|
||||
assert json.decode[i16]('201')! == 201
|
||||
|
||||
assert json.decode[i16]('-1')! == -1
|
||||
assert json.decode[i16]('-201')! == -201
|
||||
|
||||
// Test int
|
||||
assert json.decode[int]('0')! == 0
|
||||
assert json.decode[int]('1')! == 1
|
||||
assert json.decode[int]('201')! == 201
|
||||
|
||||
assert json.decode[int]('-1')! == -1
|
||||
assert json.decode[int]('-201')! == -201
|
||||
|
||||
assert json.decode[int]('1234567890')! == 1234567890
|
||||
assert json.decode[int]('-1234567890')! == -1234567890
|
||||
|
||||
// Test i64
|
||||
assert json.decode[i64]('0')! == 0
|
||||
assert json.decode[i64]('1')! == 1
|
||||
assert json.decode[i64]('201')! == 201
|
||||
|
||||
assert json.decode[i64]('-1')! == -1
|
||||
assert json.decode[i64]('-201')! == -201
|
||||
|
||||
assert json.decode[i64]('1234567890')! == 1234567890
|
||||
assert json.decode[i64]('-1234567890')! == -1234567890
|
||||
|
||||
// Test f32
|
||||
assert json.decode[f32]('0')! == 0.0
|
||||
assert json.decode[f32]('1')! == 1.0
|
||||
assert json.decode[f32]('1.2')! == 1.2
|
||||
assert json.decode[f32]('-1.2')! == -1.2
|
||||
assert json.decode[f32]('201')! == 201.0
|
||||
|
||||
assert json.decode[f32]('-1')! == -1.0
|
||||
assert json.decode[f32]('-201')! == -201.0
|
||||
|
||||
assert json.decode[f32]('1234567890')! == 1234567890.0
|
||||
assert json.decode[f32]('-1234567890')! == -1234567890.0
|
||||
|
||||
// Test f64
|
||||
assert json.decode[f64]('0')! == 0.0
|
||||
assert json.decode[f64]('1')! == 1.0
|
||||
assert json.decode[f64]('1.2')! == 1.2
|
||||
assert json.decode[f64]('201')! == 201.0
|
||||
|
||||
assert json.decode[f64]('-1')! == -1.0
|
||||
assert json.decode[f64]('-1.2')! == -1.2
|
||||
assert json.decode[f64]('-201')! == -201.0
|
||||
|
||||
assert json.decode[f64]('1234567890')! == 1234567890.0
|
||||
assert json.decode[f64]('-1234567890')! == -1234567890.0
|
||||
|
||||
assert json.decode[f64]('1e10')! == 10000000000
|
||||
assert json.decode[f64]('1E10')! == 10000000000
|
||||
assert json.decode[f64]('1e+10')! == 10000000000
|
||||
assert json.decode[f64]('1e-10')! == 0.0000000001
|
||||
assert json.decode[f64]('-1e10')! == -10000000000
|
||||
assert json.decode[f64]('-1E-10')! == -0.0000000001
|
||||
assert json.decode[f64]('0.123e3')! - 123 < 0.0000001
|
||||
assert json.decode[f64]('10.5E+3')! == 10500
|
||||
|
||||
// Test Over/Underflow
|
||||
assert json.decode[i8]('127')! == 127
|
||||
assert json.decode[i8]('-128')! == -128
|
||||
|
||||
if x := json.decode[i8]('128') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('130') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('1000') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('-129') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('-130') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[i8]('-1000') {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_boolean() {
|
||||
assert json.decode[bool]('false')! == false
|
||||
assert json.decode[bool]('true')! == true
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import x.json2 as json
|
||||
|
||||
pub struct Stru {
|
||||
val int
|
||||
val2 string
|
||||
val3 Stru2
|
||||
}
|
||||
|
||||
pub struct Stru2 {
|
||||
a int
|
||||
brazilian_steak string
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeOption[T] {
|
||||
mut:
|
||||
val ?T
|
||||
}
|
||||
|
||||
struct StructTypePointer[T] {
|
||||
mut:
|
||||
val &T
|
||||
}
|
||||
|
||||
fn test_array_of_strings() {
|
||||
// Structs
|
||||
assert json.decode[StructType[string]]('{"val": "2"}')! == StructType{
|
||||
val: '2'
|
||||
}
|
||||
assert json.decode[StructType[int]]('{"val": 2}')! == StructType{
|
||||
val: 2
|
||||
}
|
||||
|
||||
// maps
|
||||
assert json.decode[map[string]string]('{"val": "2"}')! == {
|
||||
'val': '2'
|
||||
}
|
||||
assert json.decode[map[string]int]('{"val": 2}')! == {
|
||||
'val': 2
|
||||
}
|
||||
|
||||
// nested map
|
||||
assert json.decode[map[string]map[string]string]('{"val": {"val2": "2"}}')! == {
|
||||
'val': {
|
||||
'val2': '2'
|
||||
}
|
||||
}
|
||||
|
||||
// nested struct
|
||||
assert json.decode[Stru]('{"val": 1, "val2": "lala", "val3": {"a": 2, "brazilian_steak": "leleu"}}')! == Stru{
|
||||
val: 1
|
||||
val2: 'lala'
|
||||
val3: Stru2{
|
||||
a: 2
|
||||
brazilian_steak: 'leleu'
|
||||
}
|
||||
}
|
||||
|
||||
// pretty print
|
||||
assert json.decode[Stru]('{
|
||||
"val": 1,
|
||||
"val2": "lala",
|
||||
"val3": {
|
||||
"a": 2,
|
||||
"brazilian_steak": "leleu"
|
||||
}
|
||||
}')! == Stru{
|
||||
val: 1
|
||||
val2: 'lala'
|
||||
val3: Stru2{
|
||||
a: 2
|
||||
brazilian_steak: 'leleu'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import x.json2
|
||||
import time
|
||||
|
||||
struct Person {
|
||||
mut:
|
||||
name string
|
||||
age ?int = 20
|
||||
birthday time.Time
|
||||
deathday ?time.Time
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
resp := '{"name": "Bob", "age": 20, "birthday": "2025-10-12 10:14:52"}'
|
||||
person := json2.decode[Person](resp)!
|
||||
assert '${person}' == "Person{
|
||||
name: 'Bob'
|
||||
age: Option(20)
|
||||
birthday: 2025-10-12 10:14:52
|
||||
deathday: Option(none)
|
||||
}"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import x.json2
|
||||
|
||||
struct F1 {
|
||||
f ?struct {
|
||||
a int
|
||||
}
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
j1 := json2.decode[F1]('{"f":{"a":1}}')!
|
||||
assert '${j1}' == 'F1{
|
||||
f: Option(struct {
|
||||
a: 1
|
||||
})
|
||||
}'
|
||||
|
||||
j2 := json2.decode[F1]('{}')!
|
||||
assert '${j2}' == 'F1{
|
||||
f: Option(none)
|
||||
}'
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import x.json2 as json
|
||||
|
||||
fn test_json_escape_low_chars() {
|
||||
assert json.decode[string](r'"\u001b"')! == '\u001b'
|
||||
assert json.decode[string](r'"\u000f"')! == '\u000f'
|
||||
assert json.decode[string](r'" "')! == '\u0020'
|
||||
assert json.decode[string](r'"\u0000"')! == '\u0000'
|
||||
}
|
||||
|
||||
fn test_json_string() {
|
||||
assert json.decode[string](r'"te\u2714st"')! == 'te✔st'
|
||||
assert json.decode[string](r'"te✔st"')! == 'te✔st'
|
||||
assert json.decode[string]('""')! == ''
|
||||
}
|
||||
|
||||
fn test_json_string_emoji() {
|
||||
assert json.decode[string](r'"🐈"')! == '🐈'
|
||||
assert json.decode[string](r'"💀"')! == '💀'
|
||||
assert json.decode[string](r'"🐈💀"')! == '🐈💀'
|
||||
}
|
||||
|
||||
fn test_json_string_non_ascii() {
|
||||
assert json.decode[string](r'"\u3072\u3089\u304c\u306a"')! == 'ひらがな'
|
||||
assert json.decode[string]('"a\\u3072b\\u3089c\\u304cd\\u306ae fgh"')! == 'aひbらcがdなe fgh'
|
||||
assert json.decode[string]('"\\u3072\\u3089\\u304c\\u306a"')! == 'ひらがな'
|
||||
}
|
||||
|
||||
fn test_utf8_strings_are_not_modified() {
|
||||
assert json.decode[string]('"ü"')! == 'ü'
|
||||
assert json.decode[string]('"Schilddrüsenerkrankungen"')! == 'Schilddrüsenerkrankungen'
|
||||
}
|
||||
|
||||
fn test_json_string_invalid_escapes() {
|
||||
mut has_error := false
|
||||
|
||||
json.decode[string](r'"\x"') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 3
|
||||
assert err.message == 'Syntax: unknown escape sequence'
|
||||
}
|
||||
has_error = true
|
||||
} // Invalid escape
|
||||
|
||||
assert has_error, 'Expected error'
|
||||
has_error = false
|
||||
|
||||
json.decode[string](r'"\u123"') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 3
|
||||
assert err.message == 'Syntax: short unicode escape sequence \\u123'
|
||||
}
|
||||
has_error = true
|
||||
} // Incomplete Unicode
|
||||
|
||||
assert has_error, 'Expected error'
|
||||
}
|
||||
|
||||
fn test_json_string_whitespace() {
|
||||
// Test strings with whitespace
|
||||
assert json.decode[string]('" "')! == ' '
|
||||
assert json.decode[string]('"\t\n\r"')! == '\t\n\r'
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
import math
|
||||
|
||||
const fixed_time = time.new(
|
||||
year: 2022
|
||||
month: 3
|
||||
day: 11
|
||||
hour: 13
|
||||
minute: 54
|
||||
second: 25
|
||||
)
|
||||
|
||||
type StringAlias = string
|
||||
type BoolAlias = bool
|
||||
type IntAlias = int
|
||||
type TimeAlias = time.Time
|
||||
type StructAlias = StructType[int]
|
||||
type EnumAlias = Enumerates
|
||||
|
||||
type SumTypes = StructType[string] | []SumTypes | []string | bool | string | time.Time | u32
|
||||
|
||||
enum Enumerates {
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e = 99
|
||||
f
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeOption[T] {
|
||||
mut:
|
||||
val ?T
|
||||
}
|
||||
|
||||
struct StructTypePointer[T] {
|
||||
mut:
|
||||
val &T
|
||||
}
|
||||
|
||||
fn test_types() {
|
||||
assert json.decode[StructType[string]]('{"val": ""}')!.val == ''
|
||||
|
||||
assert json.decode[StructType[string]]('{"val": "2"}')!.val == '2'
|
||||
|
||||
assert json.decode[StructType[int]]('{"val": 2}')!.val == 2
|
||||
|
||||
assert json.decode[StructType[map[string]string]]('{"val": {"val1": "test"}}')!.val['val1'] == 'test'
|
||||
|
||||
assert json.decode[StructType[Enumerates]]('{"val": 0}')!.val == Enumerates.a
|
||||
assert json.decode[StructType[Enumerates]]('{"val": 1}')!.val == Enumerates.b
|
||||
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2022-03-11T13:54:25.000Z"}')!.val == fixed_time
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2022-03-11T13:54:25.000Z"}')!.val.unix() == fixed_time.unix()
|
||||
}
|
||||
|
||||
fn test_option_types() {
|
||||
if x := json.decode[StructTypeOption[string]]('{}')!.val {
|
||||
assert false, 'Should return none'
|
||||
} else {
|
||||
assert err.msg() == ''
|
||||
assert true
|
||||
}
|
||||
|
||||
if x := json.decode[StructTypeOption[string]]('{"val": "2"}')!.val {
|
||||
assert x == '2'
|
||||
} else {
|
||||
assert false, 'Should not return none'
|
||||
}
|
||||
}
|
||||
|
||||
// Test structure for basic number types
|
||||
struct JsonNumbers {
|
||||
val_i8 i8
|
||||
val_i16 i16
|
||||
val_i32 i32
|
||||
val_i64 i64
|
||||
val_u8 u8
|
||||
val_u16 u16
|
||||
val_u32 u32
|
||||
val_u64 u64
|
||||
val_int int
|
||||
val_f32 f32
|
||||
val_f64 f64
|
||||
}
|
||||
|
||||
// Test structure for float precision
|
||||
struct JsonFloats {
|
||||
val_f32 f32
|
||||
val_f64 f64
|
||||
}
|
||||
|
||||
// Test basic functionality
|
||||
struct JsonU8 {
|
||||
val1 u8
|
||||
val2 u8
|
||||
}
|
||||
|
||||
fn test_u8_values() {
|
||||
x := JsonU8{
|
||||
val1: 58
|
||||
val2: 58
|
||||
}
|
||||
|
||||
str := json.encode(x)
|
||||
y := json.decode[JsonU8](str) or { panic(err) }
|
||||
assert x == y
|
||||
println('✓ Basic u8 test passed')
|
||||
}
|
||||
|
||||
fn test_all_number_types() {
|
||||
// Test normal range values
|
||||
x := JsonNumbers{
|
||||
val_i8: -100
|
||||
val_i16: -1000
|
||||
val_i32: -100000
|
||||
val_i64: -1000000000
|
||||
val_u8: 58
|
||||
val_u16: 2000
|
||||
val_u32: 200000
|
||||
val_u64: 2000000000
|
||||
val_int: 300000
|
||||
val_f32: 3.14
|
||||
val_f64: 2.71828
|
||||
}
|
||||
|
||||
str := json.encode(x)
|
||||
y := json.decode[JsonNumbers](str) or { panic('Decoding failed: ${err}') }
|
||||
|
||||
assert x.val_i8 == y.val_i8
|
||||
assert x.val_i16 == y.val_i16
|
||||
assert x.val_i32 == y.val_i32
|
||||
assert x.val_i64 == y.val_i64
|
||||
assert x.val_u8 == y.val_u8
|
||||
assert x.val_u16 == y.val_u16
|
||||
assert x.val_u32 == y.val_u32
|
||||
assert x.val_u64 == y.val_u64
|
||||
assert x.val_int == y.val_int
|
||||
assert math.alike(x.val_f32, y.val_f32)
|
||||
assert math.alike(x.val_f64, y.val_f64)
|
||||
|
||||
println('✓ All number types test passed')
|
||||
}
|
||||
|
||||
fn test_number_boundaries() {
|
||||
// Test minimum and maximum values for various types
|
||||
test_cases := [
|
||||
json.encode(JsonU8{ val1: 0, val2: 255 }),
|
||||
json.encode(JsonU8{ val1: u8(128), val2: u8(255) }),
|
||||
'{"min_i8": -128, "max_i8": 127}',
|
||||
'{"min_u8": 0, "max_u8": 255}',
|
||||
'{"zero": 0}',
|
||||
]!
|
||||
|
||||
for case in test_cases {
|
||||
result := json.decode[JsonU8](case)!
|
||||
assert result.val1 >= 0 && result.val1 <= 255
|
||||
assert result.val2 >= 0 && result.val2 <= 255
|
||||
}
|
||||
println('✓ Boundary values test passed')
|
||||
}
|
||||
|
||||
fn test_quoted_numbers_in_strict_mode() {
|
||||
quoted_number_cases := [
|
||||
'{"val1": "123", "val2": "45"}',
|
||||
'{"val1": 123, "val2": "45"}',
|
||||
'{"val1": "255", "val2": 0}',
|
||||
]!
|
||||
|
||||
for case in quoted_number_cases {
|
||||
json.decode[JsonU8](case, strict: true) or { continue }
|
||||
panic('Expected decoding to fail for quoted number in strict mode but succeeded: ${case}')
|
||||
}
|
||||
println('✓ Quoted numbers correctly rejected in strict mode test passed')
|
||||
}
|
||||
|
||||
fn test_quoted_numbers_in_default_mode() {
|
||||
assert json.decode[JsonU8]('{"val1": "123", "val2": "45"}')! == JsonU8{
|
||||
val1: 123
|
||||
val2: 45
|
||||
}
|
||||
assert json.decode[JsonU8]('{"val1": 123, "val2": "45"}')! == JsonU8{
|
||||
val1: 123
|
||||
val2: 45
|
||||
}
|
||||
println('✓ Quoted numbers accepted in default mode test passed')
|
||||
}
|
||||
|
||||
fn test_error_conditions() {
|
||||
// Test invalid inputs that should trigger errors
|
||||
invalid_cases := [
|
||||
'{"val1": "abc", "val2": 58}', // Non-numeric string
|
||||
'{"val1": "", "val2": 58}', // Empty string
|
||||
'{"val1": 300, "val2": 58}', // Out of u8 range (300 > 255)
|
||||
'{"val1": -1, "val2": 58}', // Negative value for u8
|
||||
'{"val1": 123.45, "val2": 58}', // Float for integer type
|
||||
]!
|
||||
|
||||
mut error_count := 0
|
||||
for case in invalid_cases {
|
||||
result := json.decode[JsonU8](case) or {
|
||||
error_count++
|
||||
continue // Expected failure, error handling works correctly
|
||||
}
|
||||
// If we reach here, it means decoding succeeded when it should have failed
|
||||
panic('Expected decoding to fail but succeeded: ${case}')
|
||||
}
|
||||
assert error_count > 0 // Ensure we caught some errors
|
||||
println('✓ Error handling test passed')
|
||||
}
|
||||
|
||||
fn test_float_precision() {
|
||||
// Test floating point precision and scientific notation
|
||||
test_cases := [
|
||||
JsonFloats{
|
||||
val_f32: 3.14159265
|
||||
val_f64: 2.718281828459045
|
||||
},
|
||||
JsonFloats{
|
||||
val_f32: 1.0e10
|
||||
val_f64: 1.0e-10
|
||||
},
|
||||
JsonFloats{
|
||||
val_f32: -123.456
|
||||
val_f64: -789.012
|
||||
},
|
||||
JsonFloats{
|
||||
val_f32: 0.0
|
||||
val_f64: 0.0
|
||||
},
|
||||
]!
|
||||
|
||||
for x in test_cases {
|
||||
str := json.encode(x)
|
||||
y := json.decode[JsonFloats](str) or { panic('Float decoding failed: ${err}') }
|
||||
|
||||
// Use relative error for assertions
|
||||
assert math.alike(y.val_f32, x.val_f32)
|
||||
assert math.alike(y.val_f64, x.val_f64)
|
||||
}
|
||||
println('✓ Float precision test passed')
|
||||
}
|
||||
|
||||
fn test_performance_large_scale() {
|
||||
// Performance test: decode large numbers of values
|
||||
mut total := u64(0)
|
||||
|
||||
for i in 0 .. 1000 {
|
||||
x := JsonU8{
|
||||
val1: u8(i % 256)
|
||||
val2: u8((i * 7) % 256)
|
||||
}
|
||||
str := json.encode(x)
|
||||
y := json.decode[JsonU8](str) or { panic('Performance test decoding failed: ${err}') }
|
||||
total += y.val1 + y.val2
|
||||
}
|
||||
|
||||
assert total > 0 // Ensure the loop executed
|
||||
println('✓ Performance test passed')
|
||||
}
|
||||
|
||||
fn test_special_float_values() {
|
||||
// Test special float values
|
||||
|
||||
// Json does not support nan, +-inf yet
|
||||
assert json.encode(math.nan()) == 'nan'
|
||||
assert json.encode(math.inf(1)) == '+inf'
|
||||
assert json.encode(math.inf(-1)) == '-inf'
|
||||
|
||||
println('✓ Special float values test passed')
|
||||
}
|
||||
|
||||
fn test_json_format_consistency() {
|
||||
// Test that encoding and decoding preserves data integrity
|
||||
original := JsonNumbers{
|
||||
val_i8: 100
|
||||
val_i16: 1000
|
||||
val_i32: 100000
|
||||
val_i64: 1000000000
|
||||
val_u8: 33
|
||||
val_u16: 2000
|
||||
val_u32: 200000
|
||||
val_u64: 2000000000
|
||||
val_int: 300000
|
||||
val_f32: 3.14
|
||||
val_f64: 2.71828
|
||||
}
|
||||
|
||||
// Encode and decode multiple times
|
||||
str1 := json.encode(original)
|
||||
decoded1 := json.decode[JsonNumbers](str1) or { panic('First decode failed: ${err}') }
|
||||
str2 := json.encode(decoded1)
|
||||
decoded2 := json.decode[JsonNumbers](str2) or { panic('Second decode failed: ${err}') }
|
||||
|
||||
// All versions should be equivalent
|
||||
assert decoded1 == decoded2
|
||||
println('✓ JSON format consistency test passed')
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import json
|
||||
|
||||
fn test_option_types() {
|
||||
assert json.decode[StructTypeOption[string]]('{}')!.val == none
|
||||
assert json.decode[StructTypeOption[string]]('{"val": ""}')!.val == ''
|
||||
assert json.decode[StructTypeOption[string]]('{"val": "0"}')!.val == '0'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": "1"}')!.val == '1'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": "2"}')!.val == '2'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": 0}')!.val == '0'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": 1}')!.val == '1'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": 2}')!.val == '2'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": "true"}')!.val == 'true'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": "false"}')!.val == 'false'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": true}')!.val == 'true'
|
||||
assert json.decode[StructTypeOption[string]]('{"val": false}')!.val == 'false'
|
||||
|
||||
assert json.decode[StructTypeOption[bool]]('{}')!.val == none
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": ""}')!.val == false
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": "0"}')!.val == false
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": "1"}')!.val == true
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": "2"}')!.val == true
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": 0}')!.val == false
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": 1}')!.val == true
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": 2}')!.val == true
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": "true"}')!.val == true
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": "false"}')!.val == false
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": true}')!.val == true
|
||||
assert json.decode[StructTypeOption[bool]]('{"val": false}')!.val == false
|
||||
|
||||
assert json.decode[StructTypeOption[int]]('{}')!.val == none
|
||||
assert json.decode[StructTypeOption[int]]('{"val": ""}')!.val == 0
|
||||
assert json.decode[StructTypeOption[int]]('{"val": "0"}')!.val == 0
|
||||
assert json.decode[StructTypeOption[int]]('{"val": "1"}')!.val == 1
|
||||
assert json.decode[StructTypeOption[int]]('{"val": "2"}')!.val == 2
|
||||
assert json.decode[StructTypeOption[int]]('{"val": 0}')!.val == 0
|
||||
assert json.decode[StructTypeOption[int]]('{"val": 1}')!.val == 1
|
||||
assert json.decode[StructTypeOption[int]]('{"val": 2}')!.val == 2
|
||||
assert json.decode[StructTypeOption[int]]('{"val": "true"}')!.val == 1
|
||||
assert json.decode[StructTypeOption[int]]('{"val": "false"}')!.val == 0
|
||||
assert json.decode[StructTypeOption[int]]('{"val": true}')!.val == 1
|
||||
assert json.decode[StructTypeOption[int]]('{"val": false}')!.val == 0
|
||||
}
|
||||
|
||||
fn test_array() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_option_array() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_alias() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_option_alias() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_sumtypes() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_option_sumtypes() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_pointer() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_caos() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn test_caos_array() {
|
||||
// TODO
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import x.json2 as json
|
||||
|
||||
fn test_raw_decode_string() {
|
||||
str := json.decode[json.Any]('"Hello!"')!
|
||||
assert str.str() == 'Hello!'
|
||||
}
|
||||
|
||||
fn test_raw_decode_string_escape() {
|
||||
jstr := json.decode[json.Any]('"\\u001b"')!
|
||||
str := jstr.str()
|
||||
assert str.len == 1
|
||||
assert str[0] == 27
|
||||
}
|
||||
|
||||
fn test_raw_decode_number() {
|
||||
num := json.decode[json.Any]('123')!
|
||||
assert num.int() == 123
|
||||
}
|
||||
|
||||
fn test_raw_decode_array() {
|
||||
raw_arr := json.decode[json.Any]('["Foo", 1]')!
|
||||
arr := raw_arr.as_array()
|
||||
assert arr[0] or { 0 }.str() == 'Foo'
|
||||
assert arr[1] or { 0 }.int() == 1
|
||||
}
|
||||
|
||||
fn test_raw_decode_bool() {
|
||||
bol := json.decode[json.Any]('false')!
|
||||
assert bol.bool() == false
|
||||
}
|
||||
|
||||
fn test_raw_decode_map() {
|
||||
raw_mp := json.decode[json.Any]('{"name":"Bob","age":20}')!
|
||||
mp := raw_mp.as_map()
|
||||
assert mp['name'] or { 0 }.str() == 'Bob'
|
||||
assert mp['age'] or { 0 }.int() == 20
|
||||
}
|
||||
|
||||
fn test_raw_decode_invalid() {
|
||||
json.decode[json.Any]('1z') or {
|
||||
assert err.msg().contains('1:2: Invalid json: Syntax: invalid value. Unexpected character after number end')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_raw_decode_string_with_dollarsign() {
|
||||
str := json.decode[json.Any](r'"Hello $world"')!
|
||||
assert str.str() == r'Hello $world'
|
||||
}
|
||||
|
||||
fn test_raw_decode_map_with_escaped_backslashes_before_string_end() {
|
||||
content := r'{"some_name": "some_value \n [ ] { } ( ) , ; ? * = ! \\@ \\"}'
|
||||
raw_mp := json.decode[json.Any](content)!
|
||||
mp := raw_mp.as_map()
|
||||
assert mp['some_name'] or { 0 }.str() == 'some_value \n [ ] { } ( ) , ; ? * = ! \\@ \\'
|
||||
}
|
||||
|
||||
fn test_raw_decode_map_with_whitespaces() {
|
||||
raw_mp := json.decode[json.Any](' \n\t{"name":"Bob","age":20}\n\t')!
|
||||
mp := raw_mp.as_map()
|
||||
assert mp['name'] or { 0 }.str() == 'Bob'
|
||||
assert mp['age'] or { 0 }.int() == 20
|
||||
}
|
||||
|
||||
fn test_raw_decode_map_invalid() {
|
||||
json.decode[json.Any]('{"name","Bob","age":20}') or {
|
||||
assert err.msg().contains('1:8: Invalid json: Syntax: expected `:`, got `,`')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_raw_decode_array_invalid() {
|
||||
json.decode[json.Any]('["Foo", 1,}') or {
|
||||
assert err.msg().contains('1:11: Invalid json: Syntax: unknown value kind')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
struct Foo {
|
||||
int []int
|
||||
str []string
|
||||
f32 []f32
|
||||
oint []?int
|
||||
}
|
||||
|
||||
// fn test_decode_array_fields() {
|
||||
// input := '{"int":[0, 1], "str":["2", "3"], "f32": [4.0, 5.0], "oint": [6, null]}'
|
||||
// foo := json.decode[Foo](input)!
|
||||
// assert foo.int == [0, 1]
|
||||
// assert foo.str == ['2', '3']
|
||||
// assert foo.f32 == [f32(4.0), 5.0]
|
||||
// a, b := foo.oint[0], foo.oint[1]
|
||||
// assert a? == 6
|
||||
// assert b? == 0
|
||||
//}
|
||||
|
||||
struct ContactItem {
|
||||
description string
|
||||
telnr string
|
||||
}
|
||||
|
||||
struct User {
|
||||
name string
|
||||
age int
|
||||
contact ContactItem
|
||||
}
|
||||
|
||||
fn test_decode_missing_comma() {
|
||||
data := '{
|
||||
"name": "Frodo",
|
||||
"age": 25
|
||||
"contact": {
|
||||
"description": "descr",
|
||||
"telnr": "+32333"
|
||||
}
|
||||
}'
|
||||
user := json.decode[User](data) or { return }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import x.json2
|
||||
|
||||
struct Disk {
|
||||
dev string
|
||||
size ?struct {
|
||||
value u64
|
||||
}
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
disk := Disk{
|
||||
size: struct {
|
||||
value: 123
|
||||
}
|
||||
}
|
||||
disk_str := json2.encode[Disk](disk)
|
||||
assert disk_str == '{"dev":"","size":{"value":123}}'
|
||||
}
|
||||
|
||||
fn test_none() {
|
||||
disk := Disk{}
|
||||
disk_str := json2.encode[Disk](disk)
|
||||
assert disk_str == '{"dev":""}'
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import x.json2
|
||||
|
||||
struct RefArrayItem {
|
||||
a int
|
||||
}
|
||||
|
||||
fn ref_array_item(value int, is_nil bool) &RefArrayItem {
|
||||
return if is_nil {
|
||||
unsafe { nil }
|
||||
} else {
|
||||
&RefArrayItem{
|
||||
a: value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_json2_encode_array_of_references_with_nil() {
|
||||
value := [
|
||||
ref_array_item(1, false),
|
||||
ref_array_item(0, true),
|
||||
ref_array_item(2, false),
|
||||
]
|
||||
assert json2.encode(value) == '[{"a":1},null,{"a":2}]'
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct Baz {
|
||||
c bool = true
|
||||
}
|
||||
|
||||
struct Foo {
|
||||
Baz
|
||||
a int = 1
|
||||
b int = 2
|
||||
}
|
||||
|
||||
struct Bar {
|
||||
Foo
|
||||
a string = '1'
|
||||
c string = '3'
|
||||
}
|
||||
|
||||
struct Environment {
|
||||
vflags string
|
||||
}
|
||||
|
||||
struct HistoryEvent {
|
||||
Environment
|
||||
}
|
||||
|
||||
struct History {
|
||||
pub mut:
|
||||
history_events []HistoryEvent
|
||||
}
|
||||
|
||||
struct Classifier {
|
||||
History
|
||||
}
|
||||
|
||||
struct RoundtripInner {
|
||||
inner []f64
|
||||
}
|
||||
|
||||
struct RoundtripOuter {
|
||||
RoundtripInner
|
||||
test f64
|
||||
}
|
||||
|
||||
struct Article {
|
||||
title string
|
||||
description string
|
||||
}
|
||||
|
||||
struct ArticlePlus {
|
||||
Article
|
||||
perex string
|
||||
}
|
||||
|
||||
fn test_embed() {
|
||||
assert json.encode(Bar{}) == '{"Foo.Baz.c":true,"Foo.a":1,"b":2,"a":"1","c":"3"}'
|
||||
assert json.encode(Bar{}, prettify: true) == '{
|
||||
"Foo.Baz.c": true,
|
||||
"Foo.a": 1,
|
||||
"b": 2,
|
||||
"a": "1",
|
||||
"c": "3"
|
||||
}'
|
||||
}
|
||||
|
||||
fn test_decode_embed_with_prefixed_keys() {
|
||||
decoded := json.decode[Bar]('{"a":"1","c":"3","Foo.a":4,"b":2,"Foo.Baz.c":false}')!
|
||||
assert decoded.a == '1'
|
||||
assert decoded.c == '3'
|
||||
assert decoded.Foo.a == 4
|
||||
assert decoded.Foo.b == 2
|
||||
assert decoded.Foo.Baz.c == false
|
||||
}
|
||||
|
||||
fn test_decode_embed_roundtrip_with_flattened_keys() {
|
||||
mut classifier := Classifier{}
|
||||
classifier.history_events = [
|
||||
HistoryEvent{
|
||||
Environment: Environment{
|
||||
vflags: 'one'
|
||||
}
|
||||
},
|
||||
HistoryEvent{
|
||||
Environment: Environment{
|
||||
vflags: 'two'
|
||||
}
|
||||
},
|
||||
]
|
||||
encoded := json.encode(classifier)
|
||||
assert encoded == '{"history_events":[{"vflags":"one"},{"vflags":"two"}]}'
|
||||
decoded := json.decode[Classifier](encoded)!
|
||||
assert decoded.history_events.len == 2
|
||||
assert decoded.history_events[0].Environment.vflags == 'one'
|
||||
assert decoded.history_events[1].Environment.vflags == 'two'
|
||||
}
|
||||
|
||||
fn test_decode_embed_with_nested_objects() {
|
||||
decoded :=
|
||||
json.decode[Classifier]('{"History":{"history_events":[{"Environment":{"vflags":"legacy"}}]}}')!
|
||||
assert decoded.history_events.len == 1
|
||||
assert decoded.history_events[0].Environment.vflags == 'legacy'
|
||||
}
|
||||
|
||||
fn test_embed_roundtrip_preserves_declaration_order() {
|
||||
str := '{"inner":[1,2,3,4,5],"test":1.2}'
|
||||
data := json.decode[RoundtripOuter](str)!
|
||||
assert json.encode(data) == str
|
||||
}
|
||||
|
||||
fn test_encode_array_of_embedded_structs_preserves_declaration_order() {
|
||||
list_of_object := [
|
||||
ArticlePlus{
|
||||
title: 'One good title'
|
||||
description: 'this is the first'
|
||||
},
|
||||
ArticlePlus{
|
||||
title: 'Other good title'
|
||||
description: 'more one'
|
||||
},
|
||||
ArticlePlus{
|
||||
title: 'Other good title'
|
||||
description: 'more one'
|
||||
perex: 'good perex'
|
||||
},
|
||||
]
|
||||
assert json.encode(list_of_object) == '[{"title":"One good title","description":"this is the first","perex":""},{"title":"Other good title","description":"more one","perex":""},{"title":"Other good title","description":"more one","perex":"good perex"}]'
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import x.json2
|
||||
|
||||
union NumberOrBytes {
|
||||
number u64
|
||||
bytes [8]u8 @[skip]
|
||||
}
|
||||
|
||||
fn test_encode_fixed_array() {
|
||||
assert json2.encode([u8(1), 2, 3]!) == '[1,2,3]'
|
||||
}
|
||||
|
||||
fn test_encode_union_with_skipped_fixed_array_field() {
|
||||
decoded := json2.decode[NumberOrBytes]('{"number":256}')!
|
||||
|
||||
unsafe {
|
||||
assert decoded.bytes == [u8(0), 1, 0, 0, 0, 0, 0, 0]!
|
||||
}
|
||||
assert json2.encode(decoded) == '{"number":256}'
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import x.json2
|
||||
|
||||
struct JoseHeader {
|
||||
pub mut:
|
||||
cty ?string
|
||||
alg string
|
||||
typ string = 'JWT'
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
res := json2.encode(JoseHeader{ alg: 'HS256' })
|
||||
assert res == '{"alg":"HS256","typ":"JWT"}'
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import time
|
||||
import x.json2 as json
|
||||
|
||||
struct StructTypeSkippedFields[T] {
|
||||
mut:
|
||||
val T @[json: '-']
|
||||
val1 T
|
||||
val2 T @[json: '-']
|
||||
val3 T
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields1[T] {
|
||||
mut:
|
||||
val T
|
||||
val1 T @[json: '-']
|
||||
val2 T
|
||||
val3 T @[json: '-']
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields2[T] {
|
||||
mut:
|
||||
val T @[json: '-']
|
||||
val1 T @[json: '-']
|
||||
val2 T @[json: '-']
|
||||
val3 T @[json: '-']
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields3[T, U, V] {
|
||||
mut:
|
||||
val T @[json: '-']
|
||||
val1 U
|
||||
val2 V
|
||||
val3 T @[json: '-']
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields4 {
|
||||
mut:
|
||||
val string @[json: '-']
|
||||
val1 i64
|
||||
val2 f64
|
||||
val3 time.Time
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields5 {
|
||||
mut:
|
||||
val string @[json: '-']
|
||||
val1 i64 @[json: '-']
|
||||
val2 f64
|
||||
val3 time.Time
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields6 {
|
||||
mut:
|
||||
val string @[json: '-']
|
||||
val1 i64
|
||||
val2 f64 @[json: '-']
|
||||
val3 time.Time
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields7 {
|
||||
mut:
|
||||
val string
|
||||
val1 i64 @[json: '-']
|
||||
val2 f64 @[json: '-']
|
||||
val3 time.Time
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields8 {
|
||||
mut:
|
||||
val string
|
||||
val1 i64 @[json: '-']
|
||||
val2 f64
|
||||
val3 time.Time @[json: '-']
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields9 {
|
||||
mut:
|
||||
val string
|
||||
val1 i64 @[skip]
|
||||
val2 f64
|
||||
val3 time.Time @[skip]
|
||||
}
|
||||
|
||||
fn test_encode_struct_skipped_fields() {
|
||||
assert json.encode(StructTypeSkippedFields[string]{
|
||||
val: 'string_val'
|
||||
val1: 'string_val1'
|
||||
val2: 'string_val2'
|
||||
val3: 'string_val3'
|
||||
}) == '{"val1":"string_val1","val3":"string_val3"}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields1[string]{
|
||||
val: 'string_val'
|
||||
val1: 'string_val1'
|
||||
val2: 'string_val2'
|
||||
val3: 'string_val3'
|
||||
}) == '{"val":"string_val","val2":"string_val2"}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields2[string]{
|
||||
val: 'string_val'
|
||||
val1: 'string_val1'
|
||||
val2: 'string_val2'
|
||||
val3: 'string_val3'
|
||||
}) == '{}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields3[string, i64, f64]{
|
||||
val: 'string_val'
|
||||
val1: 1
|
||||
val2: 1.0
|
||||
val3: 'string_val'
|
||||
}) == '{"val1":1,"val2":1}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields4{
|
||||
val: 'string_val'
|
||||
val1: 1
|
||||
val2: 1.0
|
||||
}) == '{"val1":1,"val2":1,"val3":"0000-01-01T00:00:00.000Z"}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields5{
|
||||
val: 'string_val'
|
||||
val1: 1
|
||||
val2: 1.0
|
||||
}) == '{"val2":1,"val3":"0000-01-01T00:00:00.000Z"}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields6{
|
||||
val: 'string_val'
|
||||
val1: 1
|
||||
val2: 1.0
|
||||
}) == '{"val1":1,"val3":"0000-01-01T00:00:00.000Z"}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields7{
|
||||
val: 'string_val'
|
||||
val1: 1
|
||||
val2: 1.0
|
||||
}) == '{"val":"string_val","val3":"0000-01-01T00:00:00.000Z"}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields8{
|
||||
val: 'string_val'
|
||||
val1: 1
|
||||
val2: 1.0
|
||||
}) == '{"val":"string_val","val2":1}'
|
||||
|
||||
assert json.encode(StructTypeSkippedFields9{
|
||||
val: 'string_val'
|
||||
val1: 1
|
||||
val2: 1.0
|
||||
}) == '{"val":"string_val","val2":1}'
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
// vtest build: !macos
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
const fixed_time = time.new(
|
||||
year: 2022
|
||||
month: 3
|
||||
day: 11
|
||||
hour: 13
|
||||
minute: 54
|
||||
second: 25
|
||||
)
|
||||
|
||||
type StringAlias = string
|
||||
type BoolAlias = bool
|
||||
type U8Alias = u8
|
||||
type U16Alias = u16
|
||||
type U32Alias = u32
|
||||
type U64Alias = u64
|
||||
type I8Alias = i8
|
||||
type I16Alias = i16
|
||||
type I32Alias = i32
|
||||
type I64Alias = i64
|
||||
type IntAlias = int
|
||||
type UsizeAlias = usize
|
||||
type IsizeAlias = isize
|
||||
type F32Alias = f32
|
||||
type F64Alias = f64
|
||||
type TimeAlias = time.Time
|
||||
type StructAlias = StructType[int]
|
||||
type EnumAlias = Enumerates
|
||||
|
||||
type SumTypes = StructType[string] | []SumTypes | []string | bool | int | string | time.Time
|
||||
|
||||
enum Enumerates {
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e = 99
|
||||
f
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeOption[T] {
|
||||
mut:
|
||||
val ?T
|
||||
}
|
||||
|
||||
struct StructTypePointer[T] {
|
||||
mut:
|
||||
val &T
|
||||
}
|
||||
|
||||
fn test_types() {
|
||||
assert json.encode(StructType[string]{}) == '{"val":""}'
|
||||
assert json.encode(StructType[string]{ val: '' }) == '{"val":""}'
|
||||
assert json.encode(StructType[string]{ val: 'a' }) == '{"val":"a"}'
|
||||
|
||||
assert json.encode(StructType[bool]{}) == '{"val":false}'
|
||||
assert json.encode(StructType[bool]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructType[bool]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructType[int]{}) == '{"val":0}'
|
||||
assert json.encode(StructType[int]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructType[int]{ val: 1 }) == '{"val":1}'
|
||||
|
||||
assert json.encode(StructType[time.Time]{}) == '{"val":"0000-01-01T00:00:00.000Z"}'
|
||||
assert json.encode(StructType[time.Time]{ val: fixed_time }) == '{"val":"2022-03-11T13:54:25.000Z"}'
|
||||
|
||||
assert json.encode(StructType[StructType[int]]{
|
||||
val: StructType[int]{
|
||||
val: 1
|
||||
}
|
||||
}) == '{"val":{"val":1}}'
|
||||
|
||||
assert json.encode(StructType[Enumerates]{}, enum_as_int: true) == '{"val":0}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.a },
|
||||
enum_as_int: true
|
||||
) == '{"val":0}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.d },
|
||||
enum_as_int: true
|
||||
) == '{"val":3}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.e },
|
||||
enum_as_int: true
|
||||
) == '{"val":99}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.f },
|
||||
enum_as_int: true
|
||||
) == '{"val":100}'
|
||||
}
|
||||
|
||||
fn test_option_types() {
|
||||
assert json.encode(StructTypeOption[string]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[string]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[string]{ val: '' }) == '{"val":""}'
|
||||
assert json.encode(StructTypeOption[string]{ val: 'a' }) == '{"val":"a"}'
|
||||
|
||||
assert json.encode(StructTypeOption[bool]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[bool]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[bool]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructTypeOption[bool]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructTypeOption[int]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[int]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[int]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[int]{ val: 1 }) == '{"val":1}'
|
||||
|
||||
assert json.encode(StructTypeOption[time.Time]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[time.Time]{ val: time.Time{} }) == '{"val":"0000-01-01T00:00:00.000Z"}'
|
||||
assert json.encode(StructTypeOption[time.Time]{ val: fixed_time }) == '{"val":"2022-03-11T13:54:25.000Z"}'
|
||||
|
||||
assert json.encode(StructTypeOption[StructType[int]]{
|
||||
val: StructType[int]{
|
||||
val: 1
|
||||
}
|
||||
}) == '{"val":{"val":1}}'
|
||||
|
||||
assert json.encode(StructTypeOption[Enumerates]{}) == '{}'
|
||||
}
|
||||
|
||||
fn test_array() {
|
||||
assert json.encode(StructType[[]string]{}) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]string]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]string]{ val: ['0'] }) == '{"val":["0"]}'
|
||||
assert json.encode(StructType[[]string]{ val: ['1'] }) == '{"val":["1"]}'
|
||||
|
||||
assert json.encode(StructType[[]int]{}) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]int]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]int]{ val: [0] }) == '{"val":[0]}'
|
||||
assert json.encode(StructType[[]int]{ val: [1] }) == '{"val":[1]}'
|
||||
assert json.encode(StructType[[]int]{ val: [0, 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructType[[]u8]{}) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]u8]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]u8]{ val: [u8(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructType[[]u8]{ val: [u8(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructType[[]u8]{ val: [u8(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructType[[]i64]{}) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]i64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]i64]{ val: [i64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructType[[]i64]{ val: [i64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructType[[]i64]{ val: [i64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructType[[]u64]{}) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]u64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]u64]{ val: [u64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructType[[]u64]{ val: [u64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructType[[]u64]{ val: [u64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructType[[]f64]{}) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]f64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]f64]{ val: [f64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructType[[]f64]{ val: [f64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructType[[]f64]{ val: [f64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructType[[]bool]{}) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]bool]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructType[[]bool]{ val: [true] }) == '{"val":[true]}'
|
||||
assert json.encode(StructType[[]bool]{ val: [false] }) == '{"val":[false]}'
|
||||
assert json.encode(StructType[[]bool]{ val: [false, true, false] }) == '{"val":[false,true,false]}'
|
||||
|
||||
array_of_struct := [StructType[bool]{
|
||||
val: true
|
||||
}, StructType[bool]{
|
||||
val: false
|
||||
}]
|
||||
assert json.encode(StructType[[]StructType[bool]]{ val: array_of_struct }) == '{"val":[{"val":true},{"val":false}]}'
|
||||
|
||||
assert json.encode(StructType[[][]string]{ val: [['1'], ['2']] }) == '{"val":[["1"],["2"]]}'
|
||||
|
||||
// error: cannot use `[][]string` as `[]string` in argument 1 to `x.json2.Encoder.encode_array`
|
||||
// assert json.encode(StructType[[][][]string]{ val: [[['1']]] }) == '{"val":[[["1"]]]}'
|
||||
}
|
||||
|
||||
fn test_option_array() {
|
||||
assert json.encode(StructTypeOption[[]string]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]string]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]string]{ val: ['0'] }) == '{"val":["0"]}'
|
||||
assert json.encode(StructTypeOption[[]string]{ val: ['1'] }) == '{"val":["1"]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]int]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [0] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [1] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [0, 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]u8]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [u8(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [u8(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [u8(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]i64]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [i64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [i64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [i64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]u64]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [u64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [u64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [u64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]f64]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [f64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [f64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [f64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]bool]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]bool]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]bool]{ val: [true] }) == '{"val":[true]}'
|
||||
assert json.encode(StructTypeOption[[]bool]{ val: [false] }) == '{"val":[false]}'
|
||||
assert json.encode(StructTypeOption[[]bool]{ val: [false, true, false] }) == '{"val":[false,true,false]}'
|
||||
|
||||
array_of_struct := [StructType[bool]{
|
||||
val: true
|
||||
}, StructType[bool]{
|
||||
val: false
|
||||
}]
|
||||
assert json.encode(StructTypeOption[[]StructType[bool]]{ val: array_of_struct }) == '{"val":[{"val":true},{"val":false}]}'
|
||||
|
||||
// assert json.encode(StructTypeOption[[][]int]{
|
||||
// val: [[0, 1], [0, 2, 3], [2], [5, 1]]
|
||||
// }) == '{"val":[[0,1],[0,2,3],[2],[5,1]]}'
|
||||
}
|
||||
|
||||
fn test_alias() {
|
||||
assert json.encode(StructType[StringAlias]{}) == '{"val":""}'
|
||||
assert json.encode(StructType[StringAlias]{ val: '' }) == '{"val":""}'
|
||||
assert json.encode(StructType[StringAlias]{ val: 'a' }) == '{"val":"a"}'
|
||||
|
||||
assert json.encode(StructType[BoolAlias]{}) == '{"val":false}'
|
||||
assert json.encode(StructType[BoolAlias]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructType[BoolAlias]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructType[IntAlias]{}) == '{"val":0}'
|
||||
assert json.encode(StructType[IntAlias]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructType[IntAlias]{ val: 1 }) == '{"val":1}'
|
||||
|
||||
assert json.encode(StructType[TimeAlias]{}) == '{"val":"0000-01-01T00:00:00.000Z"}'
|
||||
assert json.encode(StructType[TimeAlias]{ val: fixed_time }) == '{"val":"2022-03-11T13:54:25.000Z"}'
|
||||
|
||||
assert json.encode(StructType[StructAlias]{}) == '{"val":{"val":0}}'
|
||||
assert json.encode(StructType[StructAlias]{ val: StructType[int]{0} }) == '{"val":{"val":0}}'
|
||||
assert json.encode(StructType[StructAlias]{ val: StructType[int]{1} }) == '{"val":{"val":1}}'
|
||||
}
|
||||
|
||||
fn test_pointer() {
|
||||
mut string_initialized_with_reference := ''
|
||||
assert json.encode(StructTypePointer[string]{ val: unsafe { nil } }) == '{}'
|
||||
assert json.encode(StructTypePointer[string]{ val: &string_initialized_with_reference }) == '{"val":""}'
|
||||
string_initialized_with_reference = 'a'
|
||||
assert json.encode(StructTypePointer[string]{ val: &string_initialized_with_reference }) == '{"val":"a"}'
|
||||
|
||||
mut bool_initialized_with_reference := false
|
||||
assert json.encode(StructTypePointer[bool]{ val: unsafe { nil } }) == '{}'
|
||||
assert json.encode(StructTypePointer[bool]{ val: &bool_initialized_with_reference }) == '{"val":false}'
|
||||
bool_initialized_with_reference = true
|
||||
assert json.encode(StructTypePointer[bool]{ val: &bool_initialized_with_reference }) == '{"val":true}'
|
||||
|
||||
mut int_initialized_with_reference := int(0)
|
||||
assert json.encode(StructTypePointer[int]{ val: unsafe { nil } }) == '{}'
|
||||
assert json.encode(StructTypePointer[int]{ val: &int_initialized_with_reference }) == '{"val":0}'
|
||||
int_initialized_with_reference = 1
|
||||
assert json.encode(StructTypePointer[int]{ val: &int_initialized_with_reference }) == '{"val":1}'
|
||||
}
|
||||
|
||||
fn test_sumtypes() {
|
||||
assert json.encode(StructType[SumTypes]{}) == '{"val":{"val":"","_type":"${typeof(StructType[string]{}).name}"}}' // is_none := val.$(field.name).str() == 'unknown sum type value'
|
||||
assert json.encode(StructType[SumTypes]{ val: '' }) == '{"val":""}'
|
||||
assert json.encode(StructType[SumTypes]{ val: 'a' }) == '{"val":"a"}'
|
||||
|
||||
assert json.encode(StructType[SumTypes]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructType[SumTypes]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructType[SumTypes]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructType[SumTypes]{ val: 1 }) == '{"val":1}'
|
||||
|
||||
assert json.encode(StructType[SumTypes]{ val: fixed_time }) == '{"val":{"_type":"${typeof(time.Time{}).name.all_after_last('.')}","value":${fixed_time.unix()}}}'
|
||||
|
||||
assert json.encode(StructType[StructType[SumTypes]]{
|
||||
val: StructType[SumTypes]{
|
||||
val: 1
|
||||
}
|
||||
}) == '{"val":{"val":1}}'
|
||||
|
||||
assert json.encode(StructType[SumTypes]{
|
||||
val: StructType[string]{
|
||||
val: '111111'
|
||||
}
|
||||
}) == '{"val":{"val":"111111","_type":"${typeof(StructType[string]{}).name}"}}'
|
||||
|
||||
assert json.encode(StructType[StructType[SumTypes]]{
|
||||
val: StructType[SumTypes]{
|
||||
val: 1
|
||||
}
|
||||
}) == '{"val":{"val":1}}'
|
||||
|
||||
// assert json.encode(StructType{ val: [SumTypes('a')] }) == '{"val":["a"]}'
|
||||
// assert json.encode(StructType[SumTypes]{ val: ['a'] }) == '{"val":["a"]}'
|
||||
// assert json.encode(StructType[SumTypes]{ val: [SumTypes('a')] }) == '{"val":["a"]}'
|
||||
// assert json.encode(StructType[SumTypes]{ val: '' }) == '{"val":""}'
|
||||
}
|
||||
|
||||
fn test_maps() {
|
||||
assert json.encode(StructType[map[string]map[string]int]{}) == '{"val":{}}'
|
||||
assert json.encode(StructType[map[string]string]{
|
||||
val: {
|
||||
'1': '1'
|
||||
}
|
||||
}) == '{"val":{"1":"1"}}'
|
||||
assert json.encode(StructType[map[string]int]{
|
||||
val: {
|
||||
'1': 1
|
||||
}
|
||||
}) == '{"val":{"1":1}}'
|
||||
assert json.encode(StructType[map[string]map[string]int]{
|
||||
val: {
|
||||
'a': {
|
||||
'1': 1
|
||||
}
|
||||
}
|
||||
}) == '{"val":{"a":{"1":1}}}'
|
||||
}
|
||||
|
||||
struct AliasStruct {
|
||||
f_string StringAlias = 'hello'
|
||||
f_bool BoolAlias = true
|
||||
f_u8 U8Alias = 123
|
||||
f_u16 U16Alias = 456
|
||||
f_u32 U32Alias = 789
|
||||
f_u64 U64Alias = 112233
|
||||
f_i8 I8Alias = -123
|
||||
f_i16 I16Alias = -456
|
||||
f_i32 I32Alias = -789
|
||||
f_i64 I64Alias = -112233
|
||||
f_int IntAlias = 7788
|
||||
f_usize UsizeAlias = 9900
|
||||
f_isize IsizeAlias = -9900
|
||||
f_f32 F32Alias = -1.5
|
||||
f_f64 F64Alias = 7.25
|
||||
}
|
||||
|
||||
fn test_struct_alias() {
|
||||
s := AliasStruct{}
|
||||
str := json.encode(s)
|
||||
assert str == '{"f_string":"hello","f_bool":true,"f_u8":123,"f_u16":456,"f_u32":789,"f_u64":112233,"f_i8":-123,"f_i16":-456,"f_i32":-789,"f_i64":-112233,"f_int":7788,"f_usize":9900,"f_isize":-9900,"f_f32":-1.5,"f_f64":7.25}'
|
||||
assert json.decode[AliasStruct](str)! == s
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
const fixed_time = time.new(
|
||||
year: 2022
|
||||
month: 3
|
||||
day: 11
|
||||
hour: 13
|
||||
minute: 54
|
||||
second: 25
|
||||
)
|
||||
|
||||
type StringAlias = string
|
||||
type BoolAlias = bool
|
||||
type IntAlias = int
|
||||
type TimeAlias = time.Time
|
||||
type StructAlias = StructType[int]
|
||||
type EnumAlias = Enumerates
|
||||
|
||||
type SumTypes = bool | int | string
|
||||
|
||||
enum Enumerates {
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e = 99
|
||||
f
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeOption[T] {
|
||||
mut:
|
||||
val ?T
|
||||
}
|
||||
|
||||
struct StructTypePointer[T] {
|
||||
mut:
|
||||
val &T
|
||||
}
|
||||
|
||||
fn test_option_types() {
|
||||
assert json.encode(StructTypeOption[Enumerates]{ val: Enumerates.a }) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[Enumerates]{ val: Enumerates.d }) == '{"val":3}'
|
||||
assert json.encode(StructTypeOption[Enumerates]{ val: Enumerates.e }) == '{"val":99}'
|
||||
assert json.encode(StructTypeOption[Enumerates]{ val: Enumerates.f }) == '{"val":100}'
|
||||
}
|
||||
|
||||
fn test_option_alias() {
|
||||
assert json.encode(StructTypeOption[BoolAlias]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[BoolAlias]{}) == '{"val":false}'
|
||||
assert json.encode(StructTypeOption[BoolAlias]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructTypeOption[BoolAlias]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructTypeOption[IntAlias]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[IntAlias]{}) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[IntAlias]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[IntAlias]{ val: 1 }) == '{"val":1}'
|
||||
|
||||
assert json.encode(StructTypeOption[EnumAlias]{}) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[EnumAlias]{ val: Enumerates.a }) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[EnumAlias]{ val: Enumerates.d }) == '{"val":3}'
|
||||
assert json.encode(StructTypeOption[EnumAlias]{ val: Enumerates.e }) == '{"val":99}'
|
||||
assert json.encode(StructTypeOption[EnumAlias]{ val: Enumerates.f }) == '{"val":100}'
|
||||
}
|
||||
|
||||
fn test_alias() {
|
||||
assert json.encode(StructType[EnumAlias]{}) == '{"val":0}'
|
||||
assert json.encode(StructType[EnumAlias]{ val: Enumerates.a }) == '{"val":0}'
|
||||
assert json.encode(StructType[EnumAlias]{ val: Enumerates.d }) == '{"val":3}'
|
||||
assert json.encode(StructType[EnumAlias]{ val: Enumerates.e }) == '{"val":99}'
|
||||
assert json.encode(StructType[EnumAlias]{ val: Enumerates.f }) == '{"val":100}'
|
||||
}
|
||||
|
||||
fn test_sumtypes() {
|
||||
assert json.encode(StructType[SumTypes]{}) == '{}'
|
||||
assert json.encode(StructType[SumTypes]{ val: '' }) == '{"val":""}'
|
||||
assert json.encode(StructType[SumTypes]{ val: 'a' }) == '{"val":"a"}'
|
||||
|
||||
assert json.encode(StructType[SumTypes]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructType[SumTypes]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructType[SumTypes]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructType[SumTypes]{ val: 1 }) == '{"val":1}'
|
||||
|
||||
assert json.encode(StructType[StructType[SumTypes]]{
|
||||
val: StructType[SumTypes]{
|
||||
val: 1
|
||||
}
|
||||
}) == '{"val":{"val":1}}'
|
||||
}
|
||||
|
||||
fn test_option_sumtypes() {
|
||||
assert json.encode(StructTypeOption[SumTypes]{}) == '{}' // REVIEW
|
||||
|
||||
assert json.encode(StructTypeOption[SumTypes]{ val: '' }) == '{"val":""}'
|
||||
assert json.encode(StructTypeOption[SumTypes]{ val: 'a' }) == '{"val":"a"}'
|
||||
|
||||
assert json.encode(StructTypeOption[SumTypes]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructTypeOption[SumTypes]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructTypeOption[SumTypes]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[SumTypes]{ val: 1 }) == '{"val":1}'
|
||||
}
|
||||
|
||||
fn test_caos() {
|
||||
typed_string_struct := StructType[string]{
|
||||
val: 'a'
|
||||
}
|
||||
|
||||
// StructType
|
||||
assert json.encode(StructType[StructType]{}) == 'TODO'
|
||||
assert json.encode(StructType[StructType[string]]{ val: typed_string_struct }) == 'TODO'
|
||||
assert json.encode(StructType[StructType[StringAlias]]{ val: typed_string_struct }) == 'TODO'
|
||||
assert json.encode(StructType[StructType[SumTypes]]{ val: typed_string_struct }) == 'TODO'
|
||||
|
||||
assert json.encode(StructType[StructTypeOption]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[StructTypeOption[string]]{ val: StructType{'a'} }) == 'TODO'
|
||||
assert json.encode(StructType[StructTypeOption[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[StructTypeOption[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
assert json.encode(StructType[StructTypePointer]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[StructTypePointer[string]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[StructTypePointer[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[StructTypePointer[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
// StructTypeOptional
|
||||
assert json.encode(StructTypeOption[StructType]{}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructType[string]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructType[StringAlias]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructType[SumTypes]]{ val: '' }) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypeOption[StructTypeOption]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructTypeOption[string]]{ val: StructType{'a'} }) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructTypeOption[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructTypeOption[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypeOption[StructTypePointer]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructTypePointer[string]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructTypePointer[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[StructTypePointer[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
// StructTypePointer
|
||||
assert json.encode(StructTypePointer[StructType]{}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructType[string]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructType[StringAlias]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructType[SumTypes]]{ val: '' }) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypePointer[StructTypeOption]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructTypeOption[string]]{ val: StructType{'a'} }) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructTypeOption[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructTypeOption[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypePointer[StructTypePointer]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructTypePointer[string]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructTypePointer[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[StructTypePointer[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
}
|
||||
|
||||
fn test_caos_array() {
|
||||
typed_string_struct := [StructType[string]{
|
||||
val: 'a'
|
||||
}]
|
||||
|
||||
// StructType
|
||||
assert json.encode(StructType[[]StructType]{}) == 'TODO'
|
||||
assert json.encode(StructType[[]StructType[string]]{ val: typed_string_struct }) == 'TODO'
|
||||
assert json.encode(StructType[[]StructType[StringAlias]]{ val: typed_string_struct }) == 'TODO'
|
||||
assert json.encode(StructType[[]StructType[SumTypes]]{ val: typed_string_struct }) == 'TODO'
|
||||
|
||||
assert json.encode(StructType[[]StructTypeOption]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[[]StructTypeOption[string]]{ val: StructType{'a'} }) == 'TODO'
|
||||
assert json.encode(StructType[[]StructTypeOption[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[[]StructTypeOption[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
assert json.encode(StructType[[]StructTypePointer]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[[]StructTypePointer[string]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[[]StructTypePointer[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructType[[]StructTypePointer[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
// StructTypeOptional
|
||||
assert json.encode(StructTypeOption[[]StructType]{}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructType[string]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructType[StringAlias]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructType[SumTypes]]{ val: '' }) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypeOption[[]StructTypeOption]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructTypeOption[string]]{ val: StructType{'a'} }) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructTypeOption[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructTypeOption[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypeOption[[]StructTypePointer]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructTypePointer[string]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructTypePointer[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypeOption[[]StructTypePointer[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
// StructTypePointer
|
||||
assert json.encode(StructTypePointer[[]StructType]{}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructType[string]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructType[StringAlias]]{ val: '' }) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructType[SumTypes]]{ val: '' }) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypePointer[[]StructTypeOption]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructTypeOption[string]]{ val: StructType{'a'} }) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructTypeOption[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructTypeOption[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
|
||||
assert json.encode(StructTypePointer[[]StructTypePointer]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructTypePointer[string]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructTypePointer[StringAlias]]{typed_string_struct}) == 'TODO'
|
||||
assert json.encode(StructTypePointer[[]StructTypePointer[SumTypes]]{typed_string_struct}) == 'TODO'
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
import json2 as json
|
||||
import time
|
||||
import math.big
|
||||
|
||||
type StrAlias = string
|
||||
type BoolAlias = bool
|
||||
type IntAlias = int
|
||||
type FloatAlias = f64
|
||||
|
||||
enum TestEnum {
|
||||
a
|
||||
b @[json: 'BBB']
|
||||
c = 10
|
||||
d @[badattr: 'bad'; foobar]
|
||||
}
|
||||
|
||||
type EnumAlias = TestEnum
|
||||
|
||||
@[json_as_number]
|
||||
enum NumberedTestEnum {
|
||||
a
|
||||
b = 5
|
||||
}
|
||||
|
||||
type Sum = int | string
|
||||
type SumAlias = Sum
|
||||
|
||||
struct Basic {
|
||||
a int
|
||||
b string
|
||||
c bool
|
||||
}
|
||||
|
||||
struct NumberedEnumWrap {
|
||||
a NumberedTestEnum
|
||||
}
|
||||
|
||||
type BasicAlias = Basic
|
||||
|
||||
struct Opt {
|
||||
a ?int
|
||||
}
|
||||
|
||||
type OptAlias = Opt
|
||||
|
||||
struct OptRequiered {
|
||||
a ?int @[required]
|
||||
}
|
||||
|
||||
type OptRequieredAlias = OptRequiered
|
||||
|
||||
struct CustomString {
|
||||
data string
|
||||
}
|
||||
|
||||
pub fn (cs CustomString) to_json() string {
|
||||
return '"<<<' + cs.data + '>>>"'
|
||||
}
|
||||
|
||||
type CustomStringAlias = CustomString
|
||||
|
||||
type NullAlias = json.Null
|
||||
|
||||
type TimeAlias = time.Time
|
||||
|
||||
type BigAlias = big.Integer
|
||||
|
||||
struct NamedFields {
|
||||
a int @[json: 'id']
|
||||
name string @[json: 'Name']
|
||||
}
|
||||
|
||||
type NamedFieldsAlias = NamedFields
|
||||
|
||||
struct SkipFields {
|
||||
a int @[json: '-']
|
||||
name string @[skip]
|
||||
}
|
||||
|
||||
type SkipFieldsAlias = SkipFields
|
||||
|
||||
struct SkipSomeFields {
|
||||
a int @[json: '-']
|
||||
name string @[skip]
|
||||
hi bool = true
|
||||
}
|
||||
|
||||
type SkipSomeFieldsAlias = SkipSomeFields
|
||||
|
||||
struct PointerFields {
|
||||
next &PointerFields = unsafe { nil }
|
||||
data int
|
||||
}
|
||||
|
||||
type PointerFieldsAlias = PointerFields
|
||||
|
||||
struct F64ArrayRoundtripPayload {
|
||||
arr []f64
|
||||
}
|
||||
|
||||
struct OmitFields {
|
||||
a ?bool @[omitempty]
|
||||
b string @[omitempty]
|
||||
c int @[omitempty]
|
||||
d f64 @[omitempty]
|
||||
e ?string = '' @[omitempty]
|
||||
f ?int = 0 @[omitempty]
|
||||
g ?f64 = 0.0 @[omitempty]
|
||||
}
|
||||
|
||||
type OmitFieldsAlias = OmitFields
|
||||
|
||||
struct OmitemptyRegressionNumber {
|
||||
min int
|
||||
max int
|
||||
}
|
||||
|
||||
struct OmitemptyRegressionResp {
|
||||
options []string @[omitempty]
|
||||
metadata map[string]string @[omitempty]
|
||||
number &OmitemptyRegressionNumber = unsafe { nil } @[omitempty]
|
||||
config ?OmitemptyRegressionNumber @[omitempty]
|
||||
}
|
||||
|
||||
fn test_primitives() {
|
||||
assert json.encode('hello') == '"hello"'
|
||||
assert json.encode(StrAlias('hello')) == '"hello"'
|
||||
assert json.encode(true) == 'true'
|
||||
assert json.encode(BoolAlias(false)) == 'false'
|
||||
assert json.encode(-12345) == '-12345'
|
||||
assert json.encode(IntAlias(-12345)) == '-12345'
|
||||
assert json.encode(123.323) == '123.323'
|
||||
assert json.encode(FloatAlias(123.323)) == '123.323'
|
||||
}
|
||||
|
||||
fn test_encode_decode_struct_with_f64_array_roundtrips() ! {
|
||||
original := F64ArrayRoundtripPayload{
|
||||
arr: [0.9716157205240175, 0.9336099585062241]
|
||||
}
|
||||
encoded := json.encode(original)
|
||||
assert encoded == '{"arr":[0.9716157205240175,0.9336099585062241]}'
|
||||
assert json.decode[F64ArrayRoundtripPayload](encoded)! == original
|
||||
}
|
||||
|
||||
fn test_arrays() {
|
||||
assert json.encode([1, 2, 3, 4]) == '[1,2,3,4]'
|
||||
}
|
||||
|
||||
fn test_maps() {
|
||||
assert json.encode({
|
||||
'hi': 0
|
||||
'bye': 1
|
||||
}) == '{"hi":0,"bye":1}'
|
||||
}
|
||||
|
||||
fn test_enums() {
|
||||
assert json.encode(TestEnum.c) == '"c"'
|
||||
assert json.encode(EnumAlias(TestEnum.c)) == '"c"'
|
||||
assert json.encode(TestEnum.c, enum_as_int: true) == '10'
|
||||
assert json.encode(EnumAlias(TestEnum.c), enum_as_int: true) == '10'
|
||||
assert json.encode(TestEnum.b) == '"BBB"'
|
||||
assert json.encode(EnumAlias(TestEnum.b)) == '"BBB"'
|
||||
// Ensure that only `json: ` attrs is applied
|
||||
assert json.encode(TestEnum.d) == '"d"'
|
||||
assert json.encode(EnumAlias(TestEnum.d)) == '"d"'
|
||||
}
|
||||
|
||||
fn test_enum_json_as_number_attribute() {
|
||||
assert json.encode(NumberedTestEnum.a) == '0'
|
||||
assert json.encode(NumberedTestEnum.b) == '5'
|
||||
assert json.encode(NumberedEnumWrap{
|
||||
a: .a
|
||||
}) == '{"a":0}'
|
||||
}
|
||||
|
||||
fn test_sumtypes() {
|
||||
assert json.encode(Sum(10)) == '10'
|
||||
assert json.encode(Sum('hi')) == '"hi"'
|
||||
assert json.encode(SumAlias(10)) == '10'
|
||||
assert json.encode(SumAlias('hi')) == '"hi"'
|
||||
}
|
||||
|
||||
fn test_basic_structs() {
|
||||
assert json.encode(Basic{
|
||||
a: 10
|
||||
b: 'hi'
|
||||
c: true
|
||||
}) == '{"a":10,"b":"hi","c":true}'
|
||||
|
||||
assert json.encode(BasicAlias{
|
||||
a: 10
|
||||
b: 'hi'
|
||||
c: true
|
||||
}) == '{"a":10,"b":"hi","c":true}'
|
||||
}
|
||||
|
||||
fn test_nested() {
|
||||
assert json.encode([
|
||||
{
|
||||
'hi': Basic{ a: 1, b: 'a', c: false }
|
||||
'bye': Basic{
|
||||
a: 2
|
||||
b: 'b'
|
||||
c: true
|
||||
}
|
||||
},
|
||||
{
|
||||
'hi2': Basic{
|
||||
a: 3
|
||||
b: 'c'
|
||||
c: false
|
||||
}
|
||||
'bye2': Basic{
|
||||
a: 4
|
||||
b: 'd'
|
||||
c: true
|
||||
}
|
||||
},
|
||||
]) == '[{"hi":{"a":1,"b":"a","c":false},"bye":{"a":2,"b":"b","c":true}},{"hi2":{"a":3,"b":"c","c":false},"bye2":{"a":4,"b":"d","c":true}}]'
|
||||
assert json.encode([
|
||||
{
|
||||
'hi': Basic{ a: 1, b: 'a', c: false }
|
||||
'bye': Basic{
|
||||
a: 2
|
||||
b: 'b'
|
||||
c: true
|
||||
}
|
||||
},
|
||||
{
|
||||
'hi2': Basic{
|
||||
a: 3
|
||||
b: 'c'
|
||||
c: false
|
||||
}
|
||||
'bye2': Basic{
|
||||
a: 4
|
||||
b: 'd'
|
||||
c: true
|
||||
}
|
||||
},
|
||||
],
|
||||
prettify: true
|
||||
) == '[
|
||||
{
|
||||
"hi": {
|
||||
"a": 1,
|
||||
"b": "a",
|
||||
"c": false
|
||||
},
|
||||
"bye": {
|
||||
"a": 2,
|
||||
"b": "b",
|
||||
"c": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"hi2": {
|
||||
"a": 3,
|
||||
"b": "c",
|
||||
"c": false
|
||||
},
|
||||
"bye2": {
|
||||
"a": 4,
|
||||
"b": "d",
|
||||
"c": true
|
||||
}
|
||||
}
|
||||
]'
|
||||
}
|
||||
|
||||
fn test_string_escapes() {
|
||||
assert json.encode('normal escapes: ", \\ special control escapes: \b, \n, \f, \t, \r, other control escapes: \0, \u001b') == r'"normal escapes: \", \\ special control escapes: \b, \n, \f, \t, \r, other control escapes: \u0000, \u001b"'
|
||||
assert json.encode('ascii, é, 한, 😀, ascii') == r'"ascii, é, 한, 😀, ascii"'
|
||||
assert json.encode('ascii, é, 한, 😀, ascii', escape_unicode: true) == r'"ascii, \u00e9, \ud55c, \uD83D\ude00, ascii"'
|
||||
}
|
||||
|
||||
fn test_options() {
|
||||
assert json.encode(Opt{none}) == '{}'
|
||||
assert json.encode(Opt{99}) == '{"a":99}'
|
||||
assert json.encode(OptAlias{none}) == '{}'
|
||||
assert json.encode(OptAlias{99}) == '{"a":99}'
|
||||
|
||||
assert json.encode(OptRequiered{none}) == '{"a":null}'
|
||||
assert json.encode(OptRequiered{99}) == '{"a":99}'
|
||||
assert json.encode(OptRequieredAlias{none}) == '{"a":null}'
|
||||
assert json.encode(OptRequieredAlias{99}) == '{"a":99}'
|
||||
}
|
||||
|
||||
fn test_custom_encoders() {
|
||||
assert json.encode(CustomString{'hi'}) == '"<<<hi>>>"'
|
||||
assert json.encode(CustomStringAlias{'hi'}) == '"<<<hi>>>"'
|
||||
|
||||
assert json.encode(json.Null{}) == 'null'
|
||||
assert json.encode(NullAlias{}) == 'null'
|
||||
|
||||
assert json.encode(time.Time{}) == '"0000-01-01T00:00:00.000Z"'
|
||||
assert json.encode(TimeAlias{}) == '"0000-01-01T00:00:00.000Z"'
|
||||
|
||||
assert json.encode(big.integer_from_i64(1234567890)) == '1234567890'
|
||||
assert json.encode(BigAlias(big.integer_from_i64(1234567890))) == '1234567890'
|
||||
}
|
||||
|
||||
fn test_named_fields() {
|
||||
assert json.encode(NamedFields{ a: 1, name: 'john' }) == '{"id":1,"Name":"john"}'
|
||||
assert json.encode(NamedFieldsAlias{ a: 1, name: 'john' }) == '{"id":1,"Name":"john"}'
|
||||
}
|
||||
|
||||
fn test_skip_fields() {
|
||||
assert json.encode(SkipFields{ a: 1, name: 'john' }) == '{}'
|
||||
assert json.encode(SkipFieldsAlias{ a: 1, name: 'john' }) == '{}'
|
||||
assert json.encode(SkipFields{ a: 1, name: 'john' },
|
||||
prettify: true
|
||||
) == '{}'
|
||||
|
||||
assert json.encode(SkipSomeFields{ a: 1, name: 'john' }) == '{"hi":true}'
|
||||
assert json.encode(SkipSomeFieldsAlias{ a: 1, name: 'john' }) == '{"hi":true}'
|
||||
assert json.encode(SkipSomeFields{ a: 1, name: 'john' },
|
||||
prettify: true
|
||||
) == '{
|
||||
"hi": true
|
||||
}'
|
||||
}
|
||||
|
||||
fn test_omit_fields() {
|
||||
assert json.encode(OmitFields{}) == '{}'
|
||||
assert json.encode(OmitFieldsAlias{}) == '{}'
|
||||
|
||||
r1 := OmitemptyRegressionResp{
|
||||
options: ['first', 'second']
|
||||
}
|
||||
r2 := OmitemptyRegressionResp{
|
||||
number: &OmitemptyRegressionNumber{0, 0}
|
||||
}
|
||||
r3 := OmitemptyRegressionResp{
|
||||
metadata: {
|
||||
'kind': 'test'
|
||||
}
|
||||
}
|
||||
assert json.encode(r1) == '{"options":["first","second"]}'
|
||||
assert json.encode(r2) == '{"number":{"min":0,"max":0}}'
|
||||
assert json.encode(r3) == '{"metadata":{"kind":"test"}}'
|
||||
assert json.encode(OmitemptyRegressionResp{}) == '{}'
|
||||
assert json.encode(OmitemptyRegressionResp{
|
||||
config: OmitemptyRegressionNumber{
|
||||
min: 1
|
||||
max: 2
|
||||
}
|
||||
}) == '{"config":{"min":1,"max":2}}'
|
||||
}
|
||||
|
||||
fn test_pointer_fields() {
|
||||
assert json.encode(PointerFields{
|
||||
next: &PointerFields{
|
||||
next: &PointerFields{
|
||||
next: &PointerFields{
|
||||
data: 4
|
||||
}
|
||||
data: 3
|
||||
}
|
||||
data: 2
|
||||
}
|
||||
data: 1
|
||||
}) == '{"next":{"next":{"next":{"data":4},"data":3},"data":2},"data":1}'
|
||||
assert json.encode(PointerFieldsAlias{
|
||||
next: &PointerFieldsAlias{
|
||||
next: &PointerFieldsAlias{
|
||||
next: &PointerFieldsAlias{
|
||||
data: 4
|
||||
}
|
||||
data: 3
|
||||
}
|
||||
data: 2
|
||||
}
|
||||
data: 1
|
||||
}) == '{"next":{"next":{"next":{"data":4},"data":3},"data":2},"data":1}'
|
||||
assert json.encode(PointerFields{
|
||||
next: &PointerFields{
|
||||
next: &PointerFields{
|
||||
next: &PointerFields{
|
||||
data: 4
|
||||
}
|
||||
data: 3
|
||||
}
|
||||
data: 2
|
||||
}
|
||||
data: 1
|
||||
},
|
||||
prettify: true
|
||||
) == '{
|
||||
"next": {
|
||||
"next": {
|
||||
"next": {
|
||||
"data": 4
|
||||
},
|
||||
"data": 3
|
||||
},
|
||||
"data": 2
|
||||
},
|
||||
"data": 1
|
||||
}'
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
fn test_json_string_characters() {
|
||||
assert json.encode([u8(`/`)].bytestr()).bytes() == r'"/"'.bytes()
|
||||
assert json.encode([u8(`\\`)].bytestr()).bytes() == r'"\\"'.bytes()
|
||||
assert json.encode([u8(`"`)].bytestr()).bytes() == r'"\""'.bytes()
|
||||
assert json.encode([u8(`\n`)].bytestr()).bytes() == r'"\n"'.bytes()
|
||||
assert json.encode(r'\n\r') == r'"\\n\\r"'
|
||||
assert json.encode('\\n') == r'"\\n"'
|
||||
assert json.encode(r'\n\r\b') == r'"\\n\\r\\b"'
|
||||
assert json.encode(r'\"/').bytes() == r'"\\\"/"'.bytes()
|
||||
|
||||
assert json.encode(r'\n\r\b\f\t\\\"\/') == r'"\\n\\r\\b\\f\\t\\\\\\\"\\/"'
|
||||
|
||||
text := json.decode[json.Any](r'"\n\r\b\f\t\\\"\/"') or { '' }
|
||||
assert text.json_str() == '"\\n\\r\\b\\f\\t\\\\\\"/"'
|
||||
|
||||
assert json.encode("fn main(){nprintln('Hello World! Helo \$a')\n}") == '"fn main(){nprintln(\'Hello World! Helo \$a\')\\n}"'
|
||||
assert json.encode(' And when "\'s are in the string, along with # "') == '" And when \\"\'s are in the string, along with # \\""'
|
||||
assert json.encode('a \\\nb') == r'"a \\\nb"'
|
||||
assert json.encode('Name\tJosé\nLocation\tSF.') == '"Name\\tJosé\\nLocation\\tSF."'
|
||||
}
|
||||
|
||||
fn test_json_escape_low_chars() {
|
||||
esc := '\u001b'
|
||||
assert esc.len == 1
|
||||
text := json.Any(esc)
|
||||
assert text.json_str() == r'"\u001b"'
|
||||
|
||||
assert json.encode('\u000f') == r'"\u000f"'
|
||||
assert json.encode('\u0020') == r'" "'
|
||||
assert json.encode('\u0000') == r'"\u0000"'
|
||||
}
|
||||
|
||||
fn test_json_string() {
|
||||
text := json.Any('te✔st')
|
||||
|
||||
assert text.json_str() == r'"te✔st"'
|
||||
assert json.encode('te✔st', escape_unicode: true) == r'"te\u2714st"'
|
||||
|
||||
boolean := json.Any(true)
|
||||
assert boolean.json_str() == 'true'
|
||||
integer := json.Any(int(-5))
|
||||
assert integer.json_str() == '-5'
|
||||
u64integer := json.Any(u64(5000))
|
||||
assert u64integer.json_str() == '5000'
|
||||
i64integer := json.Any(i64(-17))
|
||||
assert i64integer.json_str() == '-17'
|
||||
}
|
||||
|
||||
fn test_json_string_emoji() {
|
||||
text := json.Any('🐈')
|
||||
assert text.json_str() == r'"🐈"'
|
||||
assert json.Any('💀').json_str() == r'"💀"'
|
||||
|
||||
assert json.encode('🐈') == r'"🐈"'
|
||||
assert json.encode('💀') == r'"💀"'
|
||||
// assert json.encode('🐈💀') == r'"🐈💀"'
|
||||
}
|
||||
|
||||
fn test_json_string_non_ascii() {
|
||||
text := json.Any('ひらがな')
|
||||
assert text.json_str() == r'"ひらがな"'
|
||||
|
||||
assert json.encode('ひらがな', escape_unicode: true) == r'"\u3072\u3089\u304c\u306a"'
|
||||
}
|
||||
|
||||
fn test_utf8_strings_are_not_modified() {
|
||||
original := '{"s":"Schilddrüsenerkrankungen"}'
|
||||
deresult := json.decode[json.Any](original)!
|
||||
assert deresult.str() == original
|
||||
|
||||
assert json.encode('ü') == '"ü"'
|
||||
assert json.encode('Schilddrüsenerkrankungen') == '"Schilddrüsenerkrankungen"'
|
||||
}
|
||||
|
||||
fn test_encoder_unescaped_utf32() ! {
|
||||
jap_text := json.Any('ひらがな')
|
||||
|
||||
assert json.encode(jap_text) == '"${jap_text}"'
|
||||
|
||||
emoji_text := json.Any('🐈')
|
||||
assert json.encode(emoji_text) == '"${emoji_text}"'
|
||||
|
||||
assert json.encode('ひらがな') == '"ひらがな"'
|
||||
}
|
||||
|
||||
fn test_encoder_prettify() {
|
||||
obj := {
|
||||
'hello': json.Any('world')
|
||||
'arr': [json.Any('im a string'), [json.Any('3rd level')]]
|
||||
'obj': {
|
||||
'map': json.Any('map inside a map')
|
||||
}
|
||||
}
|
||||
assert json.encode(obj, prettify: true, indent_string: ' ') == '{
|
||||
"hello": "world",
|
||||
"arr": [
|
||||
"im a string",
|
||||
[
|
||||
"3rd level"
|
||||
]
|
||||
],
|
||||
"obj": {
|
||||
"map": "map inside a map"
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
pub struct Test {
|
||||
val string
|
||||
}
|
||||
|
||||
struct SharedMapHolder {
|
||||
a int
|
||||
cache shared map[u64]string
|
||||
}
|
||||
|
||||
fn test_encode_struct() {
|
||||
enc := json.encode(Test{'hello!'})
|
||||
assert enc == '{"val":"hello!"}'
|
||||
}
|
||||
|
||||
fn test_encode_struct_with_shared_map_field() {
|
||||
mut holder := SharedMapHolder{
|
||||
a: 1
|
||||
}
|
||||
|
||||
rlock holder.cache {
|
||||
assert json.encode(holder) == '{"a":1,"cache":{}}'
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Uri {
|
||||
protocol string
|
||||
path string
|
||||
}
|
||||
|
||||
pub fn (u Uri) json_str() string {
|
||||
return '"${u.protocol}://${u.path}"'
|
||||
}
|
||||
|
||||
fn test_encode_encodable() {
|
||||
assert json.encode(Uri{'file', 'path/to/file'}) == '"file://path/to/file"'
|
||||
}
|
||||
|
||||
fn test_encode_array() {
|
||||
array_of_struct := [StructType[[]bool]{
|
||||
val: [false, true]
|
||||
}, StructType[[]bool]{
|
||||
val: [true, false]
|
||||
}]
|
||||
|
||||
assert json.encode([1, 2, 3]) == '[1,2,3]'
|
||||
|
||||
assert json.encode(array_of_struct) == '[{"val":[false,true]},{"val":[true,false]}]'
|
||||
}
|
||||
|
||||
fn test_encode_simple() {
|
||||
assert json.encode('hello!') == '"hello!"'
|
||||
assert json.encode(1) == '1'
|
||||
}
|
||||
|
||||
fn test_encode_value() {
|
||||
mut manifest := map[string]json.Any{}
|
||||
|
||||
manifest['server_path'] = json.Any('new_path')
|
||||
manifest['last_updated'] = json.Any('timestamp.format_ss()')
|
||||
manifest['from_source'] = json.Any('from_source')
|
||||
|
||||
assert json.encode(manifest, prettify: true, indent_string: ' ') == r'{
|
||||
"server_path": "new_path",
|
||||
"last_updated": "timestamp.format_ss()",
|
||||
"from_source": "from_source"
|
||||
}'
|
||||
}
|
||||
|
||||
fn test_encode_time() {
|
||||
assert json.encode({
|
||||
'bro': json.Any(time.Time{})
|
||||
}) == '{"bro":"0000-01-01T00:00:00.000Z"}'
|
||||
|
||||
assert json.encode({
|
||||
'bro': time.Time{}
|
||||
}) == '{"bro":"0000-01-01T00:00:00.000Z"}'
|
||||
|
||||
assert json.encode(time.Time{}) == '"0000-01-01T00:00:00.000Z"'
|
||||
}
|
||||
|
||||
fn test_encode_float() {
|
||||
// remove ending `.0`
|
||||
assert json.encode(1.0) == '1'
|
||||
|
||||
// TODO: better generate `1e10` instead of `1e+10`
|
||||
assert json.encode(1.0e+10) == '1e+10'
|
||||
assert json.encode(1.0e10) == '1e+10'
|
||||
assert json.encode(1.e10) == '1e+10'
|
||||
assert json.encode(1.0e-10) == '1e-10'
|
||||
assert json.encode(1.1e10) == '1.1e+10'
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import x.json2
|
||||
|
||||
struct IntegerValues {
|
||||
mut:
|
||||
ux8 u8
|
||||
ux16 u16
|
||||
ux32 u32
|
||||
ux64 u64
|
||||
sx8 i8
|
||||
sx16 i16
|
||||
sx32 int
|
||||
sx64 i64
|
||||
}
|
||||
|
||||
fn test_all_primitive_integer_types_are_encodable_and_decodable() {
|
||||
f := IntegerValues{1, 2, 3, 4, -1, -2, -3, -4}
|
||||
s := json2.encode[IntegerValues](f)
|
||||
assert s == '{"ux8":1,"ux16":2,"ux32":3,"ux64":4,"sx8":-1,"sx16":-2,"sx32":-3,"sx64":-4}'
|
||||
x := json2.decode[IntegerValues](s)!
|
||||
assert x == f
|
||||
println('done')
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import x.json2 as json
|
||||
|
||||
enum JobTitle {
|
||||
manager
|
||||
executive
|
||||
worker
|
||||
}
|
||||
|
||||
struct Employee {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
}
|
||||
|
||||
fn test_fast_raw_decode() {
|
||||
s := '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
|
||||
o := json.decode[json.Any](s) or {
|
||||
assert false
|
||||
json.Any('')
|
||||
}
|
||||
str := o.str()
|
||||
assert str == '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
|
||||
}
|
||||
|
||||
fn test_character_unescape() {
|
||||
message := r'{
|
||||
"newline": "new\nline",
|
||||
"tab": "\ttab",
|
||||
"backslash": "back\\slash",
|
||||
"quotes": "\"quotes\"",
|
||||
"slash":"\/dev\/null"
|
||||
}'
|
||||
mut obj := json.decode[json.Any](message) or {
|
||||
println(err)
|
||||
assert false
|
||||
return
|
||||
}
|
||||
lines := obj.as_map()
|
||||
assert lines['newline'] or { 0 }.str() == 'new\nline'
|
||||
assert lines['tab'] or { 0 }.str() == '\ttab'
|
||||
assert lines['backslash'] or { 0 }.str() == 'back\\slash'
|
||||
assert lines['quotes'] or { 0 }.str() == '"quotes"'
|
||||
assert lines['slash'] or { 0 }.str() == '/dev/null'
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
fn test_struct_with_bool_to_map() {
|
||||
array_of_struct := [StructType[bool]{
|
||||
val: true
|
||||
}, StructType[bool]{
|
||||
val: false
|
||||
}]
|
||||
|
||||
mut array_of_map := []json.Any{}
|
||||
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
|
||||
assert array_of_map.str() == '[{"val":true},{"val":false}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_string_to_map() {
|
||||
array_of_struct := [StructType[string]{
|
||||
val: 'true'
|
||||
}, StructType[string]{
|
||||
val: 'false'
|
||||
}]
|
||||
|
||||
mut array_of_map := []json.Any{}
|
||||
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
|
||||
assert array_of_map.str() == '[{"val":"true"},{"val":"false"}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_array_to_map() {
|
||||
array_of_struct := [StructType[[]bool]{
|
||||
val: [false, true]
|
||||
}, StructType[[]bool]{
|
||||
val: [true, false]
|
||||
}]
|
||||
|
||||
mut array_of_map := []json.Any{}
|
||||
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
|
||||
assert array_of_map.str() == '[{"val":[false,true]},{"val":[true,false]}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_array_of_arrays_to_map() {
|
||||
array_of_struct := [
|
||||
StructType[[][]bool]{
|
||||
val: [[true, false], [true, false]]
|
||||
},
|
||||
StructType[[][]bool]{
|
||||
val: [[false, true], [false, true]]
|
||||
},
|
||||
]
|
||||
mut array_of_map := []json.Any{}
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
assert array_of_map.str() == '[{"val":[[true,false],[true,false]]},{"val":[[false,true],[false,true]]}]'
|
||||
|
||||
array_of_struct_int := [
|
||||
StructType[[][]int]{
|
||||
val: [[1, 0], [1, 0]]
|
||||
},
|
||||
StructType[[][]int]{
|
||||
val: [[0, 1], [0, 1]]
|
||||
},
|
||||
]
|
||||
mut array_of_map_int := []json.Any{}
|
||||
for variable in array_of_struct_int {
|
||||
array_of_map_int << json.map_from(variable)
|
||||
}
|
||||
assert array_of_map_int.str() == '[{"val":[[1,0],[1,0]]},{"val":[[0,1],[0,1]]}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_number_to_map() {
|
||||
assert json.map_from(StructType[string]{'3'}).str() == '{"val":"3"}'
|
||||
assert json.map_from(StructType[bool]{true}).str() == '{"val":true}'
|
||||
assert json.map_from(StructType[i8]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[i16]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[int]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[i64]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[i8]{-3}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[i16]{i16(-3)}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[int]{-3}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[i64]{-3}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[f32]{3.0}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[f64]{3.0}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u8]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u16]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u32]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u64]{3}).str() == '{"val":3}'
|
||||
}
|
||||
|
||||
fn test_struct_with_struct_to_map() {
|
||||
assert json.map_from(StructType[StructType[string]]{StructType[string]{'3'}}).str() == '{"val":{"val":"3"}}'
|
||||
assert json.map_from(StructType[StructType[int]]{StructType[int]{3}}).str() == '{"val":{"val":3}}'
|
||||
}
|
||||
|
||||
fn test_maps() {
|
||||
assert json.decode[map[string]string]('{"test":"abc"}')! == {
|
||||
'test': 'abc'
|
||||
}
|
||||
|
||||
// assert json.decode[map[string]StructType[bool]]('{"test":{"val":true}}') or {
|
||||
// dump(err)
|
||||
// assert false
|
||||
// } == {"test":StructType[bool]{true}}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct TestTwin {
|
||||
id int
|
||||
seed string
|
||||
pubkey string
|
||||
}
|
||||
|
||||
struct TestTwins {
|
||||
mut:
|
||||
twins []TestTwin @[required]
|
||||
}
|
||||
|
||||
fn test_json_decode_fails_to_decode_unrecognised_array_of_dicts() {
|
||||
data := '[{"twins":[{"id":123,"seed":"abcde","pubkey":"xyzasd"},{"id":456,"seed":"dfgdfgdfgd","pubkey":"skjldskljh45sdf"}]}]'
|
||||
json.decode[TestTwins](data) or {
|
||||
assert err.msg() == "expected field 'twins' is missing"
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
struct User2 {
|
||||
age int
|
||||
nums []int
|
||||
reg_date time.Time
|
||||
}
|
||||
|
||||
struct User {
|
||||
age int
|
||||
nums []int
|
||||
last_name string @[json: lastName]
|
||||
is_registered bool @[json: IsRegistered]
|
||||
typ int @[json: 'type']
|
||||
pets string @[json: 'pet_animals'; raw]
|
||||
}
|
||||
|
||||
fn test_encode_decode_time() {
|
||||
user := User2{
|
||||
age: 25
|
||||
reg_date: time.new(year: 2020, month: 12, day: 22, hour: 7, minute: 23)
|
||||
}
|
||||
s := json.encode(user)
|
||||
println(s)
|
||||
assert s.contains('"reg_date":1608621780')
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct DbConfig {
|
||||
host string
|
||||
dbname string
|
||||
user string
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_empty() {
|
||||
json.decode[DbConfig]('') or {
|
||||
assert err.msg() == 'failed to decode JSON string'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct City {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Country {
|
||||
cities []City
|
||||
name string
|
||||
}
|
||||
|
||||
struct Data {
|
||||
countries []Country
|
||||
users map[string]User
|
||||
extra map[string]map[string]int
|
||||
}
|
||||
|
||||
struct User {
|
||||
age int
|
||||
nums []int
|
||||
last_name string @[json: lastName]
|
||||
is_registered bool @[json: IsRegistered]
|
||||
typ int @[json: 'type']
|
||||
pets string @[json: 'pet_animals'; raw]
|
||||
}
|
||||
|
||||
fn test_errors() {
|
||||
invalid_array := fn () {
|
||||
data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":{"name":"Donlon"},"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}'
|
||||
json.decode[Data](data) or {
|
||||
println(err)
|
||||
assert err.msg().starts_with('Json element is not an array:')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
invalid_object := fn () {
|
||||
data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":[{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}],"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}'
|
||||
json.decode[Data](data) or {
|
||||
println(err)
|
||||
assert err.msg().starts_with('Json element is not an object:')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
invalid_array()
|
||||
invalid_object()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct DbConfig {
|
||||
host string
|
||||
dbname string
|
||||
user string
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_just_brace() {
|
||||
json.decode[DbConfig]('{') or {
|
||||
assert err.msg() == 'failed to decode JSON string: {'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct Info {
|
||||
id int
|
||||
items []string
|
||||
maps map[string]string
|
||||
}
|
||||
|
||||
fn test_decode_null_object() ! {
|
||||
info := json.decode[Info]('{"id": 22, "items": null, "maps": null}')!
|
||||
assert info.id == 22
|
||||
assert '${info.items}' == '[]'
|
||||
assert '${info.maps}' == '{}'
|
||||
}
|
||||
|
||||
fn test_decode_missing_maps_field() ! {
|
||||
info := json.decode[Info]('{"id": 22, "items": null}')!
|
||||
assert info.id == 22
|
||||
assert '${info.items}' == '[]'
|
||||
assert '${info.maps}' == '{}'
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct DbConfig {
|
||||
host string
|
||||
dbname string
|
||||
user string
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_trailing_comma_at_end() {
|
||||
txt := '{
|
||||
"host": "localhost",
|
||||
"dbname": "alex",
|
||||
"user": "alex",
|
||||
}'
|
||||
json.decode[DbConfig](txt) or {
|
||||
assert err.msg().contains(' "user": "alex",\n}')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct ChildNullish {
|
||||
name string
|
||||
}
|
||||
|
||||
struct NullishStruct {
|
||||
name string
|
||||
lastname string
|
||||
age int
|
||||
salary f32
|
||||
child ChildNullish
|
||||
}
|
||||
|
||||
struct RequiredStruct {
|
||||
name string @[required]
|
||||
lastname string
|
||||
}
|
||||
|
||||
fn test_required() ! {
|
||||
nullish_one :=
|
||||
json.decode[NullishStruct]('{"name":"Peter", "lastname":null, "age":28,"salary":95000.5,"title":"worker"}')!
|
||||
assert nullish_one.name == 'Peter'
|
||||
assert nullish_one.lastname == ''
|
||||
assert nullish_one.age == 28
|
||||
assert nullish_one.salary == 95000.5
|
||||
assert nullish_one.child.name == ''
|
||||
|
||||
nullish_two :=
|
||||
json.decode[NullishStruct]('{"name":"Peter", "lastname": "Parker", "age":28,"salary":95000.5,"title":"worker"}')!
|
||||
assert nullish_two.name == 'Peter'
|
||||
assert nullish_two.lastname == 'Parker'
|
||||
assert nullish_two.age == 28
|
||||
assert nullish_two.salary == 95000.5
|
||||
assert nullish_two.child.name == ''
|
||||
|
||||
nullish_third :=
|
||||
json.decode[NullishStruct]('{"name":"Peter", "age":28,"salary":95000.5,"title":"worker", "child": {"name":"Nullish"}}')!
|
||||
assert nullish_third.name == 'Peter'
|
||||
assert nullish_third.lastname == ''
|
||||
assert nullish_third.age == 28
|
||||
assert nullish_third.salary == 95000.5
|
||||
assert nullish_third.child.name == 'Nullish'
|
||||
|
||||
required_struct := json.decode[RequiredStruct]('{"name":"Peter", "lastname": "Parker"}')!
|
||||
assert required_struct.name == 'Peter'
|
||||
assert required_struct.lastname == 'Parker'
|
||||
|
||||
required_struct_err := json.decode[RequiredStruct]('{"name": null, "lastname": "Parker"}') or {
|
||||
assert err.msg() == "type mismatch for field 'name', expecting `string` type, got: null"
|
||||
RequiredStruct{
|
||||
name: 'Peter'
|
||||
lastname: 'Parker'
|
||||
}
|
||||
}
|
||||
println(required_struct_err)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct DbConfig {
|
||||
host string
|
||||
dbname string
|
||||
user string
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_in_the_middle() {
|
||||
txt := '{"host": "localhost", "dbname": "alex" "user": "alex", "port": "1234"}'
|
||||
json.decode[DbConfig](txt) or {
|
||||
assert err.msg().contains('ost", "dbname": "alex" "user":')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import x.json2 as json
|
||||
|
||||
type Any = string | f32 | bool | ?int
|
||||
|
||||
fn test_main() {
|
||||
x := json.decode[[]Any]('["hi", -9.8e7, true, null]')!
|
||||
assert dump(x) == [Any('hi'), Any(f32(-9.8e+7)), Any(true), Any('')]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import x.json2 as json
|
||||
|
||||
type Test = []bool | []int | []string | string
|
||||
|
||||
struct Some {
|
||||
t Test
|
||||
}
|
||||
|
||||
fn test_json_decode_with_sumtype() {
|
||||
v1 := json.decode[Some]('{"t": ["string", "string2"]}')!
|
||||
println(v1)
|
||||
assert v1.t == Test(['string', 'string2'])
|
||||
|
||||
v2 := json.decode[Some]('{"t": [11, 22]}')!
|
||||
println(v2)
|
||||
assert v2.t == Test([11, 22])
|
||||
|
||||
v3 := json.decode[Some]('{"t": [true, false]}')!
|
||||
println(v3)
|
||||
assert v3.t == Test([true, false])
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct UnicodeString {
|
||||
emoji string
|
||||
}
|
||||
|
||||
fn test_encode_unicode_as_ascii_escape_sequences() {
|
||||
valid_json := r'{"emoji":"\u3007"}'
|
||||
decoded := json.decode[UnicodeString](valid_json)!
|
||||
assert decoded.emoji == '〇'
|
||||
assert json.encode(UnicodeString{
|
||||
emoji: '〇'
|
||||
}) == valid_json
|
||||
assert json.encode('😀') == r'"\uD83D\ude00"'
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import x.json2 as json
|
||||
|
||||
pub struct DocumentFindFilter[T] {
|
||||
pub:
|
||||
selector ?T
|
||||
selector2 ?[]T
|
||||
selector3 ?[1]T
|
||||
limit ?int
|
||||
skip ?int
|
||||
fields ?[]string // This breaks the compiler when encoding to JSON
|
||||
conflicts ?bool
|
||||
read_quorum ?int @[json: r]
|
||||
update ?bool
|
||||
stable ?bool
|
||||
stale ?string
|
||||
execution_stats ?bool
|
||||
}
|
||||
|
||||
fn test_string() {
|
||||
t := DocumentFindFilter[string]{
|
||||
selector: 'aa'
|
||||
selector2: ['a', 'b']
|
||||
selector3: ['z']!
|
||||
}
|
||||
|
||||
assert json.encode(t) == '{"selector":"aa","selector2":["a","b"],"selector3":["z"]}'
|
||||
assert json.decode[DocumentFindFilter[string]]('{"selector":"aa","selector2":["a","b"],"selector3":["z"]}')! == t
|
||||
}
|
||||
|
||||
fn test_int() ! {
|
||||
t := DocumentFindFilter[int]{
|
||||
selector: 1
|
||||
selector2: [1, 2]
|
||||
selector3: [3]!
|
||||
}
|
||||
|
||||
assert json.encode(t) == '{"selector":1,"selector2":[1,2],"selector3":[3]}'
|
||||
assert json.decode[DocumentFindFilter[int]]('{"selector":1,"selector2":[1,2],"selector3":[3]}')! == t
|
||||
}
|
||||
|
||||
fn test_none() ! {
|
||||
t := DocumentFindFilter[int]{}
|
||||
|
||||
assert json.encode(t) == '{}'
|
||||
assert json.decode[DocumentFindFilter[int]]('{}')! == t
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct Test {
|
||||
optional_alias ?MyAlias // primitive
|
||||
optional_struct ?MyAlias2 // complex
|
||||
}
|
||||
|
||||
struct Complex {
|
||||
a int = 3
|
||||
}
|
||||
|
||||
type MyAlias = int
|
||||
type MyAlias2 = Complex
|
||||
|
||||
fn test_empty() {
|
||||
test := Test{}
|
||||
encoded := json.encode(test)
|
||||
assert dump(encoded) == '{}'
|
||||
assert json.decode[Test]('{}')! == test
|
||||
}
|
||||
|
||||
fn test_value() {
|
||||
test := Test{
|
||||
optional_alias: 1
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
assert dump(encoded) == '{"optional_alias":1}'
|
||||
assert json.decode[Test]('{"optional_alias":1}')! == test
|
||||
}
|
||||
|
||||
fn test_value_2() {
|
||||
test := Test{
|
||||
optional_alias: 1
|
||||
optional_struct: Complex{
|
||||
a: 1
|
||||
}
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
assert dump(encoded) == '{"optional_alias":1,"optional_struct":{"a":1}}'
|
||||
assert json.decode[Test]('{"optional_alias":1,"optional_struct":{"a":1}}')! == test
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
module main
|
||||
|
||||
import x.json2 as json
|
||||
|
||||
struct MyStruct {
|
||||
name string // should fail
|
||||
age ?int
|
||||
active bool
|
||||
}
|
||||
|
||||
struct TestStructOne {
|
||||
property_one string @[json: 'propertyOne']
|
||||
property_two string @[json: 'propertyTwo']
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
mut errors := 0
|
||||
json.decode[MyStruct]('{ "name": 1}') or {
|
||||
errors++
|
||||
assert err.msg() == "type mismatch for field 'name', expecting `string` type, got: 1"
|
||||
}
|
||||
json.decode[MyStruct]('{ "name": "John Doe", "age": ""}') or {
|
||||
errors++
|
||||
assert err.msg() == 'type mismatch for field \'age\', expecting `?int` type, got: ""'
|
||||
}
|
||||
json.decode[MyStruct]('{ "name": "John Doe", "age": 1, "active": ""}') or {
|
||||
errors++
|
||||
assert err.msg() == 'type mismatch for field \'active\', expecting `bool` type, got: ""'
|
||||
}
|
||||
res := json.decode[MyStruct]('{ "name": "John Doe", "age": "1"}') or { panic(err) }
|
||||
assert errors == 3
|
||||
assert res.name == 'John Doe'
|
||||
}
|
||||
|
||||
fn test_decode_object_into_string_field() {
|
||||
payload := '{"propertyOne":"property_two should stay a regular string {}","propertyTwo":{}}'
|
||||
res := json.decode[TestStructOne](payload) or { panic(err) }
|
||||
assert res.property_one == 'property_two should stay a regular string {}'
|
||||
assert res.property_two == '{}'
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct TestRawStringifiedObject {
|
||||
metadata string @[raw]
|
||||
}
|
||||
|
||||
fn test_stringified_object_returns_error_for_raw_field() {
|
||||
stringified_json :=
|
||||
json.encode('{"metadata":{"topLevelProperty":{"nestedProperty1":"Value 1"}}}')
|
||||
json.decode[TestRawStringifiedObject](stringified_json) or {
|
||||
assert err.msg().starts_with('Json element is not an object:')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import x.json2 as json
|
||||
|
||||
type Prices = Price | []Price
|
||||
|
||||
pub struct ShopResponseData {
|
||||
attributes Attributes
|
||||
}
|
||||
|
||||
struct Attributes {
|
||||
price ?Prices
|
||||
}
|
||||
|
||||
struct Price {
|
||||
net f64
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
data3 := json.encode(ShopResponseData{
|
||||
attributes: Attributes{
|
||||
price: Prices([Price{
|
||||
net: 1.2
|
||||
}])
|
||||
}
|
||||
})
|
||||
assert data3 == '{"attributes":{"price":[{"net":1.2,"_type":"Price"}]}}'
|
||||
|
||||
entity3 := json.decode[ShopResponseData](data3) or { panic(err) }
|
||||
assert entity3 == ShopResponseData{
|
||||
attributes: Attributes{
|
||||
price: Prices([Price{
|
||||
net: 1.2
|
||||
}])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct AnyStruct[T] {
|
||||
val T
|
||||
}
|
||||
|
||||
struct OptAnyStruct[T] {
|
||||
val ?T
|
||||
}
|
||||
|
||||
// struct OptAnyArrStruct {
|
||||
// val []?json.Any
|
||||
// }
|
||||
|
||||
fn test_values() {
|
||||
assert json.decode[AnyStruct[json.Any]]('{"val":5}')!.val.int() == 5
|
||||
assert json.decode[OptAnyStruct[json.Any]]('{}')!.val == none
|
||||
assert json.decode[OptAnyStruct[json.Any]]('{"val":null}')!.val == none
|
||||
assert json.decode[AnyStruct[[]json.Any]]('{"val":[5,10]}')!.val.map(it.int()) == [
|
||||
5,
|
||||
10,
|
||||
]
|
||||
// assert json.decode[OptAnyArrStruct]('{"val":[5,null,10]}')!.val == [?json.Any(5),json.Null{},10] // skipped because test still fails even though they're the same
|
||||
|
||||
assert json.encode[AnyStruct[json.Any]](AnyStruct[json.Any]{json.Any(5)}) == '{"val":5}'
|
||||
assert json.encode[OptAnyStruct[json.Any]](OptAnyStruct[json.Any]{none}) == '{}'
|
||||
assert json.encode[AnyStruct[[]json.Any]](AnyStruct[[]json.Any]{[json.Any(5), 10]}) == '{"val":[5,10]}'
|
||||
// assert json.encode[OptAnyArrStruct](OptAnyArrStruct{[?json.Any(5),none,10]}) == '{"val":[5,null,10]}' // encode_array has not implemented optional arrays yet
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import x.json2 as json
|
||||
|
||||
const data = '
|
||||
{
|
||||
"comments": {
|
||||
"26788945": {
|
||||
"id": "26788945",
|
||||
"message": "some comment 1"
|
||||
},
|
||||
"26788946": {
|
||||
"id": "26788946",
|
||||
"message": "some comment 2"
|
||||
},
|
||||
"26788947": {
|
||||
"id": "26788947",
|
||||
"message": "some comment 3"
|
||||
}
|
||||
},
|
||||
"comments2": {
|
||||
"26788945": true,
|
||||
"26788946": false,
|
||||
"26788947": true
|
||||
},
|
||||
"comments3": {
|
||||
"26788945": 1,
|
||||
"26788946": 2,
|
||||
"26788947": 3
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
pub struct Comment {
|
||||
id string
|
||||
message string
|
||||
}
|
||||
|
||||
struct Comments {
|
||||
mut:
|
||||
comments map[string]Comment
|
||||
comments2 map[string]bool
|
||||
comments3 map[string]int
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
mut root := json.decode[Comments](data)!
|
||||
assert root.comments.len == 3
|
||||
assert root.comments['26788945']!.id == '26788945'
|
||||
assert root.comments['26788946']!.id == '26788946'
|
||||
assert root.comments['26788947']!.id == '26788947'
|
||||
|
||||
assert root.comments2['26788945']! == true
|
||||
assert root.comments2['26788946']! == false
|
||||
assert root.comments2['26788947']! == true
|
||||
|
||||
assert root.comments3['26788945']! == 1
|
||||
assert root.comments3['26788946']! == 2
|
||||
assert root.comments3['26788947']! == 3
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
const fixed_time = time.new(
|
||||
year: 2022
|
||||
month: 3
|
||||
day: 11
|
||||
hour: 13
|
||||
minute: 54
|
||||
second: 25
|
||||
)
|
||||
|
||||
type StringAlias = string
|
||||
type BoolAlias = bool
|
||||
type IntAlias = int
|
||||
|
||||
type SumTypes = bool | int | string
|
||||
|
||||
enum Enumerates {
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e = 99
|
||||
f
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeSub {
|
||||
test string
|
||||
}
|
||||
|
||||
struct StructTypeOption[T] {
|
||||
mut:
|
||||
val ?T
|
||||
}
|
||||
|
||||
struct StructTypePointer[T] {
|
||||
mut:
|
||||
val &T
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields[T] {
|
||||
mut:
|
||||
val T @[json: '-']
|
||||
val1 T
|
||||
val2 T @[json: '-']
|
||||
val3 T
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields2[T] {
|
||||
mut:
|
||||
val T
|
||||
val1 T @[json: '-']
|
||||
val2 T
|
||||
val3 T @[json: '-']
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields3[T] {
|
||||
mut:
|
||||
val T @[json: '-']
|
||||
val1 T @[json: '-']
|
||||
val2 T @[json: '-']
|
||||
val3 T @[json: '-']
|
||||
}
|
||||
|
||||
struct StructTypeSkippedField4 {
|
||||
mut:
|
||||
val map[string]string @[json: '-']
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields5[T] {
|
||||
mut:
|
||||
val T @[skip]
|
||||
val1 T @[skip]
|
||||
val2 T @[skip]
|
||||
val3 T @[skip]
|
||||
}
|
||||
|
||||
struct StructTypeSkippedFields6[T] {
|
||||
mut:
|
||||
val T
|
||||
val1 T @[skip]
|
||||
val2 T
|
||||
val3 T @[skip]
|
||||
}
|
||||
|
||||
fn test_types() {
|
||||
assert json.decode[StructType[string]]('{"val": ""}')!.val == ''
|
||||
assert json.decode[StructType[string]]('{"val": "0"}')!.val == '0'
|
||||
assert json.decode[StructType[string]]('{"val": "1"}')!.val == '1'
|
||||
assert json.decode[StructType[string]]('{"val": "2"}')!.val == '2'
|
||||
// assert json.decode[StructType[string]]('{"val": 0}')!.val == '0' // This should be a error
|
||||
// assert json.decode[StructType[string]]('{"val": 1}')!.val == '1' // This should be a error
|
||||
// assert json.decode[StructType[string]]('{"val": 2}')!.val == '2' // This should be a error
|
||||
assert json.decode[StructType[string]]('{"val": "true"}')!.val == 'true'
|
||||
assert json.decode[StructType[string]]('{"val": "false"}')!.val == 'false'
|
||||
// assert json.decode[StructType[string]]('{"val": true}')!.val == 'true' // This should be a error
|
||||
// assert json.decode[StructType[string]]('{"val": false}')!.val == 'false' // This should be a error
|
||||
|
||||
// assert json.decode[StructType[bool]]('{"val": ""}')!.val == false // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": "0"}')!.val == false // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": "1"}')!.val == true // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": "2"}')!.val == true // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": 0}')!.val == false // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": 1}')!.val == true // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": 2}')!.val == true // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": "true"}')!.val == true // This should be a error
|
||||
// assert json.decode[StructType[bool]]('{"val": "false"}')!.val == false // This should be a error
|
||||
assert json.decode[StructType[bool]]('{"val": true}')!.val == true
|
||||
assert json.decode[StructType[bool]]('{"val": false}')!.val == false
|
||||
|
||||
// assert json.decode[StructType[int]]('{"val": ""}')!.val == 0 // This should be a error
|
||||
// assert json.decode[StructType[int]]('{"val": "0"}')!.val == 0 // This should be a error
|
||||
// assert json.decode[StructType[int]]('{"val": "1"}')!.val == 1 // This should be a error
|
||||
// assert json.decode[StructType[int]]('{"val": "2"}')!.val == 2 // This should be a error
|
||||
assert json.decode[StructType[int]]('{"val": 0}')!.val == 0
|
||||
assert json.decode[StructType[int]]('{"val": 1}')!.val == 1
|
||||
assert json.decode[StructType[int]]('{"val": 2}')!.val == 2
|
||||
// assert json.decode[StructType[int]]('{"val": "true"}')!.val == 0 // This should be a error
|
||||
// assert json.decode[StructType[int]]('{"val": "false"}')!.val == 0 // This should be a error
|
||||
// assert json.decode[StructType[int]]('{"val": true}')!.val == 1 // This should be a error
|
||||
// assert json.decode[StructType[int]]('{"val": false}')!.val == 0 // This should be a error
|
||||
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2022-03-11T13:54:25.000Z"}')!.val == fixed_time
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2001-01-05"}')!.val.year == 2001
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2001-01-05"}')!.val.month == 1
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2001-01-05"}')!.val.day == 5
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2001-01-05"}')!.val.hour == 0
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2001-01-05"}')!.val.minute == 0
|
||||
assert json.decode[StructType[time.Time]]('{"val": "2001-01-05"}')!.val.second == 0
|
||||
|
||||
assert json.decode[StructType[StructTypeSub]]('{"val": {"test": "test"}}')!.val.test == 'test'
|
||||
|
||||
assert json.decode[StructType[Enumerates]]('{"val": 0}')!.val == .a
|
||||
assert json.decode[StructType[Enumerates]]('{"val": 1}')!.val == .b
|
||||
assert json.decode[StructType[Enumerates]]('{"val": 99}')!.val == .e
|
||||
assert json.decode[StructType[Enumerates]]('{}')!.val == .a
|
||||
|
||||
if x := json.decode[StructTypeOption[Enumerates]]('{"val": 0}')!.val {
|
||||
assert x == .a
|
||||
}
|
||||
if x := json.decode[StructTypeOption[Enumerates]]('{"val": 1}')!.val {
|
||||
assert x == .b
|
||||
}
|
||||
if x := json.decode[StructTypeOption[Enumerates]]('{"val": 99}')!.val {
|
||||
assert x == .e
|
||||
}
|
||||
if x := json.decode[StructTypeOption[Enumerates]]('{}')!.val {
|
||||
assert false
|
||||
} else {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
|
||||
fn test_skipped_fields() {
|
||||
if x := json.decode[StructTypeSkippedFields[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
|
||||
assert x.val == 0
|
||||
assert x.val1 == 10
|
||||
assert x.val2 == 0
|
||||
assert x.val3 == 10
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
|
||||
if x := json.decode[StructTypeSkippedFields2[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
|
||||
assert x.val == 10
|
||||
assert x.val1 == 0
|
||||
assert x.val2 == 10
|
||||
assert x.val3 == 0
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
|
||||
if x := json.decode[StructTypeSkippedFields3[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
|
||||
assert x.val == 0
|
||||
assert x.val1 == 0
|
||||
assert x.val2 == 0
|
||||
assert x.val3 == 0
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
|
||||
if x := json.decode[StructTypeSkippedField4]('{"val":{"a":"b"}}') {
|
||||
assert x.val.len == 0
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
|
||||
if x := json.decode[StructTypeSkippedFields5[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
|
||||
assert x.val == 0
|
||||
assert x.val1 == 0
|
||||
assert x.val2 == 0
|
||||
assert x.val3 == 0
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
|
||||
if x := json.decode[StructTypeSkippedFields6[int]]('{"val":10,"val1":10,"val2":10,"val3":10}') {
|
||||
assert x.val == 10
|
||||
assert x.val1 == 0
|
||||
assert x.val2 == 10
|
||||
assert x.val3 == 0
|
||||
} else {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import x.json2 as json
|
||||
|
||||
fn test_raw_decode_string() {
|
||||
str := json.decode[json.Any]('"Hello!"')!
|
||||
assert str.str() == 'Hello!'
|
||||
}
|
||||
|
||||
fn test_raw_decode_string_escape() {
|
||||
jstr := json.decode[json.Any]('"\u001b"')!
|
||||
str := jstr.str()
|
||||
assert str.len == 1
|
||||
assert str[0] == 27
|
||||
}
|
||||
|
||||
fn test_raw_decode_number() {
|
||||
num := json.decode[json.Any]('123')!
|
||||
assert num.int() == 123
|
||||
}
|
||||
|
||||
fn test_raw_decode_array() {
|
||||
raw_arr := json.decode[json.Any]('["Foo", 1]')!
|
||||
arr := raw_arr.as_array()
|
||||
assert arr[0] or { 0 }.str() == 'Foo'
|
||||
assert arr[1] or { 0 }.int() == 1
|
||||
}
|
||||
|
||||
fn test_raw_decode_bool() {
|
||||
bol := json.decode[json.Any]('false')!
|
||||
assert bol.bool() == false
|
||||
}
|
||||
|
||||
fn test_raw_decode_map() {
|
||||
raw_mp := json.decode[json.Any]('{"name":"Bob","age":20}')!
|
||||
mp := raw_mp.as_map()
|
||||
assert mp['name'] or { 0 }.str() == 'Bob'
|
||||
assert mp['age'] or { 0 }.int() == 20
|
||||
}
|
||||
|
||||
fn test_raw_decode_string_with_dollarsign() {
|
||||
str := json.decode[json.Any](r'"Hello $world"')!
|
||||
assert str.str() == r'Hello $world'
|
||||
}
|
||||
|
||||
fn test_raw_decode_map_with_whitespaces() {
|
||||
raw_mp := json.decode[json.Any](' \n\t{"name":"Bob","age":20}\n\t')!
|
||||
mp := raw_mp.as_map()
|
||||
assert mp['name'] or { 0 }.str() == 'Bob'
|
||||
assert mp['age'] or { 0 }.int() == 20
|
||||
}
|
||||
|
||||
fn test_raw_decode_map_invalid() {
|
||||
json.decode[json.Any]('{"name","Bob","age":20}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 8
|
||||
assert err.message == 'Syntax: expected `:`, got `,`'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_raw_decode_array_invalid() {
|
||||
json.decode[json.Any]('["Foo", 1,}') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 11
|
||||
assert err.message == 'Syntax: unknown value kind'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import x.json2 as json
|
||||
|
||||
fn test_utf8_strings_are_not_modified() {
|
||||
original := '{"s":"Schilddrüsenerkrankungen"}'
|
||||
deresult := json.decode[json.Any](original)!
|
||||
assert deresult.str() == original
|
||||
|
||||
assert json.encode('ü') == '"ü"'
|
||||
assert json.encode('Schilddrüsenerkrankungen') == '"Schilddrüsenerkrankungen"'
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import x.json2 as json
|
||||
|
||||
enum JobTitle {
|
||||
manager
|
||||
executive
|
||||
worker
|
||||
}
|
||||
|
||||
struct Employee {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
}
|
||||
|
||||
fn test_fast_raw_decode() {
|
||||
s := '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
|
||||
o := json.decode[json.Any](s) or {
|
||||
assert false
|
||||
json.Any('')
|
||||
}
|
||||
str := o.str()
|
||||
assert str == '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
fn test_struct_with_bool_to_map() {
|
||||
array_of_struct := [StructType[bool]{
|
||||
val: true
|
||||
}, StructType[bool]{
|
||||
val: false
|
||||
}]
|
||||
|
||||
mut array_of_map := []json.Any{}
|
||||
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
|
||||
assert array_of_map.str() == '[{"val":true},{"val":false}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_string_to_map() {
|
||||
array_of_struct := [StructType[string]{
|
||||
val: 'true'
|
||||
}, StructType[string]{
|
||||
val: 'false'
|
||||
}]
|
||||
|
||||
mut array_of_map := []json.Any{}
|
||||
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
|
||||
assert array_of_map.str() == '[{"val":"true"},{"val":"false"}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_array_to_map() {
|
||||
array_of_struct := [StructType[[]bool]{
|
||||
val: [false, true]
|
||||
}, StructType[[]bool]{
|
||||
val: [true, false]
|
||||
}]
|
||||
|
||||
mut array_of_map := []json.Any{}
|
||||
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
|
||||
assert array_of_map.str() == '[{"val":[false,true]},{"val":[true,false]}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_array_of_arrays_to_map() {
|
||||
array_of_struct := [
|
||||
StructType[[][]bool]{
|
||||
val: [[true, false], [true, false]]
|
||||
},
|
||||
StructType[[][]bool]{
|
||||
val: [[false, true], [false, true]]
|
||||
},
|
||||
]
|
||||
mut array_of_map := []json.Any{}
|
||||
for variable in array_of_struct {
|
||||
array_of_map << json.map_from(variable)
|
||||
}
|
||||
assert array_of_map.str() == '[{"val":[[true,false],[true,false]]},{"val":[[false,true],[false,true]]}]'
|
||||
|
||||
array_of_struct_int := [
|
||||
StructType[[][]int]{
|
||||
val: [[1, 0], [1, 0]]
|
||||
},
|
||||
StructType[[][]int]{
|
||||
val: [[0, 1], [0, 1]]
|
||||
},
|
||||
]
|
||||
mut array_of_map_int := []json.Any{}
|
||||
for variable in array_of_struct_int {
|
||||
array_of_map_int << json.map_from(variable)
|
||||
}
|
||||
assert array_of_map_int.str() == '[{"val":[[1,0],[1,0]]},{"val":[[0,1],[0,1]]}]'
|
||||
}
|
||||
|
||||
fn test_struct_with_number_to_map() {
|
||||
assert json.map_from(StructType[string]{'3'}).str() == '{"val":"3"}'
|
||||
assert json.map_from(StructType[bool]{true}).str() == '{"val":true}'
|
||||
assert json.map_from(StructType[i8]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[i16]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[int]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[i64]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[i8]{-3}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[i16]{i16(-3)}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[int]{-3}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[i64]{-3}).str() == '{"val":-3}'
|
||||
assert json.map_from(StructType[f32]{3.0}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[f64]{3.0}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u8]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u16]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u32]{3}).str() == '{"val":3}'
|
||||
assert json.map_from(StructType[u64]{3}).str() == '{"val":3}'
|
||||
}
|
||||
|
||||
fn test_struct_with_struct_to_map() {
|
||||
assert json.map_from(StructType[StructType[string]]{StructType[string]{'3'}}).str() == '{"val":{"val":"3"}}'
|
||||
assert json.map_from(StructType[StructType[int]]{StructType[int]{3}}).str() == '{"val":{"val":3}}'
|
||||
}
|
||||
|
||||
fn test_maps() {
|
||||
assert json.decode[map[string]string]('{"test":"abc"}')! == {
|
||||
'test': 'abc'
|
||||
}
|
||||
|
||||
assert json.decode[map[string]StructType[bool]]('{"test":{"val":true}}')! == {
|
||||
'test': StructType[bool]{true}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct Mount {
|
||||
size u64
|
||||
}
|
||||
|
||||
fn test_decode_u64() {
|
||||
data := '{"size": 10737418240}'
|
||||
m := json.decode[Mount](data)!
|
||||
assert m.size == 10737418240
|
||||
}
|
||||
|
||||
pub struct Comment {
|
||||
pub mut:
|
||||
id string
|
||||
comment string
|
||||
}
|
||||
|
||||
pub struct Task {
|
||||
mut:
|
||||
description string
|
||||
id int
|
||||
total_comments int
|
||||
file_name string @[skip]
|
||||
comments []Comment @[skip]
|
||||
}
|
||||
|
||||
fn test_skip_fields_should_be_initialised_by_json_decode() {
|
||||
data := '{"total_comments": 55, "id": 123}'
|
||||
mut task := json.decode[Task](data)!
|
||||
assert task.id == 123
|
||||
assert task.total_comments == 55
|
||||
assert task.comments == []
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
struct DbConfig {
|
||||
host string
|
||||
dbname string
|
||||
user string
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_empty() {
|
||||
json.decode[DbConfig]('') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'empty string'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_just_brace() {
|
||||
json.decode[DbConfig]('{') or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 1
|
||||
assert err.message == 'Syntax: EOF: expected object end'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_trailing_comma_at_end() {
|
||||
txt := '{
|
||||
"host": "localhost",
|
||||
"dbname": "alex",
|
||||
"user": "alex",
|
||||
}'
|
||||
|
||||
json.decode[DbConfig](txt) or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 5
|
||||
assert err.character == 1
|
||||
assert err.message == 'Syntax: Cannot use `,`, before `}`'
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_in_the_middle() {
|
||||
txt := '{"host": "localhost", "dbname": "alex" "user": "alex", "port": "1234"}'
|
||||
json.decode[DbConfig](txt) or {
|
||||
if err is json.JsonDecodeError {
|
||||
assert err.line == 1
|
||||
assert err.character == 40
|
||||
assert err.message == 'Syntax: invalid value. Unexpected character after string end'
|
||||
}
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
enum JobTitle {
|
||||
manager
|
||||
executive
|
||||
worker
|
||||
}
|
||||
|
||||
struct Employee {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
sub_employee SubEmployee //! FIXME - decode
|
||||
}
|
||||
|
||||
pub struct SubEmployee {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
}
|
||||
|
||||
fn test_simple() {
|
||||
sub_employee := SubEmployee{
|
||||
name: 'João'
|
||||
}
|
||||
x := Employee{'Peter', 28, 95000.5, .worker, sub_employee}
|
||||
s := json.encode[Employee](x, enum_as_int: true)
|
||||
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2,"sub_employee":{"name":"João","age":0,"salary":0,"title":0}}'
|
||||
|
||||
y := json.decode[Employee](s) or {
|
||||
println(err)
|
||||
assert false
|
||||
return
|
||||
}
|
||||
assert y.name == 'Peter'
|
||||
assert y.age == 28
|
||||
assert y.salary == 95000.5
|
||||
assert y.title == .worker
|
||||
// assert y.sub_employee.name == 'João'
|
||||
assert y.sub_employee.age == 0
|
||||
assert y.sub_employee.salary == 0.0
|
||||
// assert y.sub_employee.title == .worker //! FIXME
|
||||
}
|
||||
|
||||
// const currency_id = 'cconst'
|
||||
|
||||
// struct Price {
|
||||
// net f64
|
||||
// currency_id string @[json: currencyId] = currency_id
|
||||
// }
|
||||
|
||||
struct User2 {
|
||||
mut:
|
||||
age int
|
||||
nums []int
|
||||
reg_date time.Time
|
||||
}
|
||||
|
||||
// User struct needs to be `pub mut` for now in order to access and manipulate values
|
||||
pub struct User {
|
||||
pub mut:
|
||||
age int
|
||||
nums []int
|
||||
last_name string @[json: lastName]
|
||||
is_registered bool @[json: IsRegistered]
|
||||
typ int @[json: 'type']
|
||||
pets string @[json: 'pet_animals'; raw]
|
||||
}
|
||||
|
||||
fn test_parse_user() {
|
||||
s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}'
|
||||
|
||||
u := json.decode[User](s)!
|
||||
|
||||
assert u.age == 10
|
||||
assert u.last_name == 'Johnson'
|
||||
assert u.is_registered == true
|
||||
// assert u.nums.len == 3
|
||||
// assert u.nums[0] == 1
|
||||
// assert u.nums[1] == 2
|
||||
// assert u.nums[2] == 3
|
||||
assert u.typ == 1
|
||||
assert u.pets == '{"name": "Bob", "animal": "Dog"}'
|
||||
}
|
||||
|
||||
fn test_encode_decode_time() {
|
||||
user := User2{
|
||||
age: 25
|
||||
reg_date: time.new(year: 2020, month: 12, day: 22, hour: 7, minute: 23)
|
||||
}
|
||||
s := json.encode(user)
|
||||
|
||||
assert s.contains('"reg_date":"2020-12-22T07:23:00.000Z"')
|
||||
}
|
||||
|
||||
fn (mut u User) foo() string {
|
||||
return json.encode(u)
|
||||
}
|
||||
|
||||
fn test_encode_user() {
|
||||
mut usr := User{
|
||||
age: 10
|
||||
nums: [1, 2, 3]
|
||||
last_name: 'Johnson'
|
||||
is_registered: true
|
||||
typ: 0
|
||||
pets: 'foo'
|
||||
}
|
||||
expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}'
|
||||
out := json.encode[User](usr)
|
||||
// println(out)
|
||||
assert out == expected
|
||||
// Test json.encode on mutable pointers
|
||||
assert usr.foo() == expected
|
||||
}
|
||||
|
||||
struct Color {
|
||||
pub mut:
|
||||
space string
|
||||
point string @[raw]
|
||||
}
|
||||
|
||||
fn test_raw_json_field() {
|
||||
color := json.decode[Color]('{"space": "YCbCr", "point": {"Y": 123}}') or {
|
||||
assert false
|
||||
Color{}
|
||||
}
|
||||
assert color.point == '{"Y": 123}'
|
||||
assert color.space == 'YCbCr'
|
||||
}
|
||||
|
||||
fn test_bad_raw_json_field() {
|
||||
color := json.decode[Color]('{"space": "YCbCr"}') or { return }
|
||||
assert color.point == ''
|
||||
assert color.space == 'YCbCr'
|
||||
}
|
||||
|
||||
fn test_encode_map() {
|
||||
expected := '{"one":1,"two":2,"three":3,"four":4}'
|
||||
numbers := {
|
||||
'one': json.Any(1)
|
||||
'two': json.Any(2)
|
||||
'three': json.Any(3)
|
||||
'four': json.Any(4)
|
||||
}
|
||||
// numbers := {
|
||||
// 'one': 1
|
||||
// 'two': 2
|
||||
// 'three': 3
|
||||
// 'four': 4
|
||||
// }
|
||||
assert json.encode(numbers) == expected
|
||||
assert numbers.str() == expected
|
||||
}
|
||||
|
||||
type ID = string
|
||||
type GG = int
|
||||
|
||||
struct Message {
|
||||
id ID
|
||||
ij GG
|
||||
}
|
||||
|
||||
fn test_encode_alias_struct() {
|
||||
expected := '{"id":"118499178790780929","ij":999998888}'
|
||||
msg := Message{'118499178790780929', 999998888}
|
||||
out := json.encode[Message](msg)
|
||||
assert out == expected
|
||||
}
|
||||
|
||||
struct StByteArray {
|
||||
ba []u8
|
||||
}
|
||||
|
||||
fn test_byte_array() {
|
||||
assert json.encode(StByteArray{ ba: [u8(1), 2, 3, 4, 5] }) == '{"ba":[1,2,3,4,5]}'
|
||||
}
|
||||
|
||||
struct Bar {
|
||||
x string
|
||||
}
|
||||
|
||||
fn bar[T](payload string) !Bar { // ?T doesn't work currently
|
||||
result := json.decode[T](payload)!
|
||||
return result
|
||||
}
|
||||
|
||||
fn test_generic() {
|
||||
result := bar[Bar]('{"x":"test"}') or { Bar{} }
|
||||
assert result.x == 'test'
|
||||
}
|
||||
|
||||
struct Foo[T] {
|
||||
pub:
|
||||
name string
|
||||
data T
|
||||
}
|
||||
|
||||
fn test_generic_struct() {
|
||||
foo_int := Foo[int]{'bar', 12}
|
||||
foo_enc := json.encode(foo_int)
|
||||
assert foo_enc == '{"name":"bar","data":12}'
|
||||
foo_dec := json.decode[Foo[int]](foo_enc)!
|
||||
assert foo_dec.name == 'bar'
|
||||
assert foo_dec.data == 12
|
||||
}
|
||||
|
||||
type StringAlias = string
|
||||
|
||||
// TODO: encode_pretty array, sum types, struct, alias of struct and others...
|
||||
struct Foo2 {
|
||||
ux8 u8
|
||||
ux16 u16
|
||||
ux32 u32
|
||||
ux64 u64
|
||||
sx8 i8
|
||||
sx16 i16
|
||||
sx32 int
|
||||
sx64 i64
|
||||
a bool
|
||||
b string
|
||||
c StringAlias
|
||||
}
|
||||
|
||||
fn test_pretty() {
|
||||
foo := Foo2{1, 2, 3, 4, -1, -2, -3, -4, true, 'abc', 'aliens'}
|
||||
assert json.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"ux8": 1,
|
||||
"ux16": 2,
|
||||
"ux32": 3,
|
||||
"ux64": 4,
|
||||
"sx8": -1,
|
||||
"sx16": -2,
|
||||
"sx32": -3,
|
||||
"sx64": -4,
|
||||
"a": true,
|
||||
"b": "abc",
|
||||
"c": "aliens"
|
||||
}'
|
||||
}
|
||||
|
||||
struct Aa {
|
||||
sub AliasType
|
||||
}
|
||||
|
||||
struct Bb {
|
||||
a int
|
||||
}
|
||||
|
||||
type AliasType = Bb
|
||||
|
||||
fn test_encode_alias_field() {
|
||||
s := json.encode(Aa{
|
||||
sub: Bb{
|
||||
a: 1
|
||||
}
|
||||
})
|
||||
assert s == '{"sub":{"a":1}}'
|
||||
}
|
||||
|
||||
struct APrice {}
|
||||
|
||||
pub struct Association {
|
||||
association &Association = unsafe { nil }
|
||||
price APrice
|
||||
}
|
||||
|
||||
fn test_encoding_struct_with_pointers() {
|
||||
value := Association{
|
||||
association: &Association{
|
||||
price: APrice{}
|
||||
}
|
||||
price: APrice{}
|
||||
}
|
||||
// println(value)
|
||||
assert json.encode(value) == '{"association":{"price":{}},"price":{}}'
|
||||
}
|
||||
|
||||
pub struct City {
|
||||
mut:
|
||||
name string
|
||||
}
|
||||
|
||||
pub struct Country {
|
||||
mut:
|
||||
cities []City
|
||||
name string
|
||||
}
|
||||
|
||||
struct Data {
|
||||
mut:
|
||||
countries []Country
|
||||
users map[string]User
|
||||
extra map[string]map[string]int
|
||||
}
|
||||
|
||||
fn test_nested_type() {
|
||||
data_expected := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}'
|
||||
data := Data{
|
||||
countries: [
|
||||
Country{
|
||||
name: 'UK'
|
||||
cities: [City{'London'}, City{'Manchester'}]
|
||||
},
|
||||
Country{
|
||||
name: 'KU'
|
||||
cities: [City{'Donlon'}, City{'Termanches'}]
|
||||
},
|
||||
]
|
||||
users: {
|
||||
'Foo': User{
|
||||
age: 10
|
||||
nums: [1, 2, 3]
|
||||
last_name: 'Johnson'
|
||||
is_registered: true
|
||||
typ: 0
|
||||
pets: 'little foo'
|
||||
}
|
||||
'Boo': User{
|
||||
age: 20
|
||||
nums: [5, 3, 1]
|
||||
last_name: 'Smith'
|
||||
is_registered: false
|
||||
typ: 4
|
||||
pets: 'little boo'
|
||||
}
|
||||
}
|
||||
extra: {
|
||||
'2': {
|
||||
'n1': 2
|
||||
'n2': 4
|
||||
'n3': 8
|
||||
'n4': 16
|
||||
}
|
||||
'3': {
|
||||
'n1': 3
|
||||
'n2': 9
|
||||
'n3': 27
|
||||
'n4': 81
|
||||
}
|
||||
}
|
||||
}
|
||||
out := json.encode(data)
|
||||
assert out == data_expected
|
||||
}
|
||||
|
||||
struct Human {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Item {
|
||||
tag string
|
||||
}
|
||||
|
||||
enum Animal {
|
||||
dog // Will be encoded as `0`
|
||||
cat
|
||||
}
|
||||
|
||||
type Entity = Animal | Human | Item | string | time.Time
|
||||
|
||||
struct SomeGame {
|
||||
title string
|
||||
player Entity
|
||||
other []Entity
|
||||
}
|
||||
|
||||
fn test_encode_decode_sumtype() {
|
||||
t := time.now()
|
||||
game := SomeGame{
|
||||
title: 'Super Mega Game'
|
||||
player: Human{'Monke'}
|
||||
other: [
|
||||
Entity(Item{'Pen'}),
|
||||
Item{'Cookie'},
|
||||
Animal.cat,
|
||||
'Stool',
|
||||
t,
|
||||
]
|
||||
}
|
||||
|
||||
enc := json.encode(game, enum_as_int: true)
|
||||
|
||||
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},1,"Stool",{"_type":"${typeof(time.Time{}).name.all_after_last('.')}","value":${t.unix()}}]}'
|
||||
|
||||
dec := json.decode[SomeGame](enc)!
|
||||
|
||||
assert game.title == dec.title
|
||||
assert game.player == dec.player
|
||||
assert (game.other[2] as Animal) == .cat
|
||||
assert dec.other[2] == Entity(Animal.cat)
|
||||
assert (game.other[4] as time.Time).unix() == (dec.other[4] as time.Time).unix()
|
||||
}
|
||||
|
||||
struct Foo3 {
|
||||
name string
|
||||
age int @[omitempty]
|
||||
}
|
||||
|
||||
// fn test_omit_empty() {
|
||||
// foo := Foo3{'Bob', 0}
|
||||
// assert json.encode(foo) == '{"name":"Bob"}'
|
||||
// assert json.encode_pretty(foo) == '{
|
||||
// "name": "Bob"
|
||||
// }'
|
||||
// }
|
||||
|
||||
struct Foo31 {
|
||||
name string
|
||||
age ?int
|
||||
}
|
||||
|
||||
fn test_option_instead_of_omit_empty() {
|
||||
foo := Foo31{
|
||||
name: 'Bob'
|
||||
}
|
||||
assert json.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"name": "Bob"
|
||||
}'
|
||||
}
|
||||
|
||||
struct Asdasd {
|
||||
data GamePacketData
|
||||
}
|
||||
|
||||
type GamePacketData = GPEquipItem | GPScale
|
||||
|
||||
struct GPScale {
|
||||
value f32
|
||||
}
|
||||
|
||||
struct GPEquipItem {
|
||||
name string
|
||||
}
|
||||
|
||||
fn create_game_packet(data &GamePacketData) string {
|
||||
return json.encode(data)
|
||||
}
|
||||
|
||||
fn test_encode_sumtype_defined_ahead() {
|
||||
ret := create_game_packet(&GamePacketData(GPScale{}))
|
||||
assert ret == '{"value":0,"_type":"GPScale"}'
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct TestTwin {
|
||||
id int
|
||||
seed string
|
||||
pubkey string
|
||||
}
|
||||
|
||||
struct TestTwins {
|
||||
mut:
|
||||
twins []TestTwin @[required]
|
||||
}
|
||||
|
||||
fn test_json_decode_fails_to_decode_unrecognised_array_of_dicts() {
|
||||
data := '[{"twins":[{"id":123,"seed":"abcde","pubkey":"xyzasd"},{"id":456,"seed":"dfgdfgdfgd","pubkey":"skjldskljh45sdf"}]}]'
|
||||
json.decode[TestTwins](data) or {
|
||||
assert err.msg() == "expected field 'twins' is missing"
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_json_decode_works_with_a_dict_of_arrays() {
|
||||
data := '{"twins":[{"id":123,"seed":"abcde","pubkey":"xyzasd"},{"id":456,"seed":"dfgdfgdfgd","pubkey":"skjldskljh45sdf"}]}'
|
||||
res := json.decode[TestTwins](data) or {
|
||||
assert false
|
||||
exit(1)
|
||||
}
|
||||
assert res.twins[0].id == 123
|
||||
assert res.twins[0].seed == 'abcde'
|
||||
assert res.twins[0].pubkey == 'xyzasd'
|
||||
assert res.twins[1].id == 456
|
||||
assert res.twins[1].seed == 'dfgdfgdfgd'
|
||||
assert res.twins[1].pubkey == 'skjldskljh45sdf'
|
||||
}
|
||||
|
||||
struct Mount {
|
||||
size u64
|
||||
}
|
||||
|
||||
fn test_decode_u64() {
|
||||
data := '{"size": 10737418240}'
|
||||
m := json.decode[Mount](data)!
|
||||
assert m.size == 10737418240
|
||||
// println(m)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
pub struct Comment {
|
||||
pub mut:
|
||||
id string
|
||||
comment string
|
||||
}
|
||||
|
||||
pub struct Task {
|
||||
mut:
|
||||
description string
|
||||
id int
|
||||
total_comments int
|
||||
file_name string @[skip]
|
||||
comments []Comment @[skip]
|
||||
}
|
||||
|
||||
fn test_skip_fields_should_be_initialised_by_json_decode() {
|
||||
data := '{"total_comments": 55, "id": 123}'
|
||||
mut task := json.decode[Task](data)!
|
||||
assert task.id == 123
|
||||
assert task.total_comments == 55
|
||||
assert task.comments == []
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
struct DbConfig {
|
||||
host string
|
||||
dbname string
|
||||
user string
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_empty() {
|
||||
json.decode[DbConfig]('') or {
|
||||
assert err.msg().len < 2
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_just_brace() {
|
||||
json.decode[DbConfig]('{') or {
|
||||
assert err.msg() == '{'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_trailing_comma_at_end() {
|
||||
txt := '{
|
||||
"host": "localhost",
|
||||
"dbname": "alex",
|
||||
"user": "alex",
|
||||
}'
|
||||
json.decode[DbConfig](txt) or {
|
||||
assert err.msg() == ' "user": "alex",\n}'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_decode_error_message_should_have_enough_context_in_the_middle() {
|
||||
txt := '{"host": "localhost", "dbname": "alex" "user": "alex", "port": "1234"}'
|
||||
json.decode[DbConfig](txt) or {
|
||||
assert err.msg() == 'ost", "dbname": "alex" "user":'
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct TodoDto {
|
||||
foo int
|
||||
}
|
||||
|
||||
fn test_decode_with_encode_arg() {
|
||||
body := TodoDto{}
|
||||
ret := json.decode[TodoDto](json.encode(body))!
|
||||
println(ret)
|
||||
assert ret.foo == 0
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct Result[T] {
|
||||
ok bool
|
||||
result T
|
||||
}
|
||||
|
||||
struct User {
|
||||
id int
|
||||
username string
|
||||
}
|
||||
|
||||
fn func[T]() !T {
|
||||
text := '{"ok": true, "result":{"id":37467243, "username": "ciao"}}'
|
||||
a := json.decode[Result[T]](text)!
|
||||
return a.result
|
||||
}
|
||||
|
||||
fn test_decode_with_generic_struct() {
|
||||
ret := func[User]()!
|
||||
println(ret)
|
||||
assert ret.id == 37467243
|
||||
assert ret.username == 'ciao'
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import x.json2 as json
|
||||
import os
|
||||
|
||||
struct DbConfig {}
|
||||
|
||||
fn test_json_decode_with_option_arg() {
|
||||
if ret := print_info() {
|
||||
println(ret)
|
||||
} else {
|
||||
println(err)
|
||||
}
|
||||
assert true
|
||||
}
|
||||
|
||||
fn print_info() !string {
|
||||
dbconf := json.decode[DbConfig](os.read_file('dbconf.json')!)!
|
||||
println(dbconf)
|
||||
return '${dbconf}'
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import x.json2 as json
|
||||
|
||||
type Test = []bool | []int | []string | string
|
||||
|
||||
struct Some {
|
||||
t Test
|
||||
}
|
||||
|
||||
fn test_json_decode_with_sumtype() {
|
||||
v1 := json.decode[Some]('{"t": ["string", "string2"]}')!
|
||||
println(v1)
|
||||
assert v1.t == Test(['string', 'string2'])
|
||||
|
||||
v2 := json.decode[Some]('{"t": [11, 22]}')!
|
||||
println(v2)
|
||||
assert v2.t == Test([11, 22])
|
||||
|
||||
v3 := json.decode[Some]('{"t": [true, false]}')!
|
||||
println(v3)
|
||||
assert v3.t == Test([true, false])
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
enum JobTitle {
|
||||
manager
|
||||
executive
|
||||
worker
|
||||
}
|
||||
|
||||
struct Employee {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
sub_employee SubEmployee //! FIXME - decode
|
||||
}
|
||||
|
||||
pub struct SubEmployee {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
}
|
||||
|
||||
fn test_simple() {
|
||||
sub_employee := SubEmployee{
|
||||
name: 'João'
|
||||
}
|
||||
x := Employee{'Peter', 28, 95000.5, .worker, sub_employee}
|
||||
s := json.encode[Employee](x, enum_as_int: true)
|
||||
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2,"sub_employee":{"name":"João","age":0,"salary":0,"title":0}}'
|
||||
|
||||
y := json.decode[Employee](s) or {
|
||||
println(err)
|
||||
assert false
|
||||
return
|
||||
}
|
||||
assert y.name == 'Peter'
|
||||
assert y.age == 28
|
||||
assert y.salary == 95000.5
|
||||
assert y.title == .worker
|
||||
// assert y.sub_employee.name == 'João'
|
||||
assert y.sub_employee.age == 0
|
||||
assert y.sub_employee.salary == 0.0
|
||||
// assert y.sub_employee.title == .worker //! FIXME
|
||||
}
|
||||
|
||||
// const currency_id = 'cconst'
|
||||
|
||||
// struct Price {
|
||||
// net f64
|
||||
// currency_id string @[json: currencyId] = currency_id
|
||||
// }
|
||||
|
||||
struct User2 {
|
||||
mut:
|
||||
age int
|
||||
nums []int
|
||||
reg_date time.Time
|
||||
}
|
||||
|
||||
// User struct needs to be `pub mut` for now in order to access and manipulate values
|
||||
pub struct User {
|
||||
pub mut:
|
||||
age int
|
||||
nums []int
|
||||
last_name string @[json: lastName]
|
||||
is_registered bool @[json: IsRegistered]
|
||||
typ int @[json: 'type']
|
||||
pets string @[json: 'pet_animals'; raw]
|
||||
}
|
||||
|
||||
fn test_parse_user() {
|
||||
s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}'
|
||||
|
||||
u := json.decode[User](s)!
|
||||
|
||||
assert u.age == 10
|
||||
assert u.last_name == 'Johnson'
|
||||
assert u.is_registered == true
|
||||
// assert u.nums.len == 3
|
||||
// assert u.nums[0] == 1
|
||||
// assert u.nums[1] == 2
|
||||
// assert u.nums[2] == 3
|
||||
assert u.typ == 1
|
||||
assert u.pets == '{"name": "Bob", "animal": "Dog"}'
|
||||
}
|
||||
|
||||
fn test_encode_decode_time() {
|
||||
user := User2{
|
||||
age: 25
|
||||
reg_date: time.new(year: 2020, month: 12, day: 22, hour: 7, minute: 23)
|
||||
}
|
||||
s := json.encode(user)
|
||||
assert s == '{"age":25,"nums":[],"reg_date":"2020-12-22T07:23:00.000Z"}'
|
||||
|
||||
assert s.contains('"reg_date":"2020-12-22T07:23:00.000Z"')
|
||||
user2 := json.decode[User2](s)!
|
||||
assert user2.reg_date.str() == '2020-12-22 07:23:00'
|
||||
}
|
||||
|
||||
fn (mut u User) foo() string {
|
||||
return json.encode(u)
|
||||
}
|
||||
|
||||
fn test_encode_user() {
|
||||
mut usr := User{
|
||||
age: 10
|
||||
nums: [1, 2, 3]
|
||||
last_name: 'Johnson'
|
||||
is_registered: true
|
||||
typ: 0
|
||||
pets: 'foo'
|
||||
}
|
||||
expected := '{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"foo"}'
|
||||
out := json.encode[User](usr)
|
||||
// println(out)
|
||||
assert out == expected
|
||||
// Test json.encode on mutable pointers
|
||||
assert usr.foo() == expected
|
||||
}
|
||||
|
||||
struct Color {
|
||||
pub mut:
|
||||
space string
|
||||
point string @[raw]
|
||||
}
|
||||
|
||||
fn test_raw_json_field() {
|
||||
color := json.decode[Color]('{"space": "YCbCr", "point": {"Y": 123}}') or {
|
||||
assert false
|
||||
Color{}
|
||||
}
|
||||
assert color.point == '{"Y": 123}'
|
||||
assert color.space == 'YCbCr'
|
||||
}
|
||||
|
||||
fn test_bad_raw_json_field() {
|
||||
color := json.decode[Color]('{"space": "YCbCr"}') or { return }
|
||||
assert color.point == ''
|
||||
assert color.space == 'YCbCr'
|
||||
}
|
||||
|
||||
fn test_encode_map() {
|
||||
expected := '{"one":1,"two":2,"three":3,"four":4}'
|
||||
numbers := {
|
||||
'one': json.Any(1)
|
||||
'two': json.Any(2)
|
||||
'three': json.Any(3)
|
||||
'four': json.Any(4)
|
||||
}
|
||||
// numbers := {
|
||||
// 'one': 1
|
||||
// 'two': 2
|
||||
// 'three': 3
|
||||
// 'four': 4
|
||||
// }
|
||||
assert json.encode(numbers) == expected
|
||||
assert numbers.str() == expected
|
||||
}
|
||||
|
||||
type ID = string
|
||||
type GG = int
|
||||
|
||||
struct Message {
|
||||
id ID
|
||||
ij GG
|
||||
}
|
||||
|
||||
fn test_encode_alias_struct() {
|
||||
expected := '{"id":"118499178790780929","ij":999998888}'
|
||||
msg := Message{'118499178790780929', 999998888}
|
||||
out := json.encode[Message](msg)
|
||||
assert out == expected
|
||||
}
|
||||
|
||||
struct StByteArray {
|
||||
ba []u8
|
||||
}
|
||||
|
||||
fn test_byte_array() {
|
||||
assert json.encode(StByteArray{ ba: [u8(1), 2, 3, 4, 5] }) == '{"ba":[1,2,3,4,5]}'
|
||||
}
|
||||
|
||||
struct Bar {
|
||||
x string
|
||||
}
|
||||
|
||||
fn bar[T](payload string) !Bar { // ?T doesn't work currently
|
||||
result := json.decode[T](payload)!
|
||||
return result
|
||||
}
|
||||
|
||||
fn test_generic() {
|
||||
result := bar[Bar]('{"x":"test"}') or { Bar{} }
|
||||
assert result.x == 'test'
|
||||
}
|
||||
|
||||
struct Foo[T] {
|
||||
pub:
|
||||
name string
|
||||
data T
|
||||
}
|
||||
|
||||
fn test_generic_struct() {
|
||||
foo_int := Foo[int]{'bar', 12}
|
||||
foo_enc := json.encode(foo_int)
|
||||
assert foo_enc == '{"name":"bar","data":12}'
|
||||
foo_dec := json.decode[Foo[int]](foo_enc)!
|
||||
assert foo_dec.name == 'bar'
|
||||
assert foo_dec.data == 12
|
||||
}
|
||||
|
||||
type StringAlias = string
|
||||
|
||||
// TODO: encode_pretty array, sum types, struct, alias of struct and others...
|
||||
struct Foo2 {
|
||||
ux8 u8
|
||||
ux16 u16
|
||||
ux32 u32
|
||||
ux64 u64
|
||||
sx8 i8
|
||||
sx16 i16
|
||||
sx32 int
|
||||
sx64 i64
|
||||
a bool
|
||||
b string
|
||||
c StringAlias
|
||||
}
|
||||
|
||||
fn test_pretty() {
|
||||
foo := Foo2{1, 2, 3, 4, -1, -2, -3, -4, true, 'abc', 'aliens'}
|
||||
assert json.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"ux8": 1,
|
||||
"ux16": 2,
|
||||
"ux32": 3,
|
||||
"ux64": 4,
|
||||
"sx8": -1,
|
||||
"sx16": -2,
|
||||
"sx32": -3,
|
||||
"sx64": -4,
|
||||
"a": true,
|
||||
"b": "abc",
|
||||
"c": "aliens"
|
||||
}'
|
||||
}
|
||||
|
||||
struct Aa {
|
||||
sub AliasType
|
||||
}
|
||||
|
||||
struct Bb {
|
||||
a int
|
||||
}
|
||||
|
||||
type AliasType = Bb
|
||||
|
||||
fn test_encode_alias_field() {
|
||||
s := json.encode(Aa{
|
||||
sub: Bb{
|
||||
a: 1
|
||||
}
|
||||
})
|
||||
assert s == '{"sub":{"a":1}}'
|
||||
}
|
||||
|
||||
struct APrice {}
|
||||
|
||||
pub struct Association {
|
||||
association &Association = unsafe { nil }
|
||||
price APrice
|
||||
}
|
||||
|
||||
fn test_encoding_struct_with_pointers() {
|
||||
value := Association{
|
||||
association: &Association{
|
||||
price: APrice{}
|
||||
}
|
||||
price: APrice{}
|
||||
}
|
||||
// println(value)
|
||||
assert json.encode(value) == '{"association":{"price":{}},"price":{}}'
|
||||
}
|
||||
|
||||
pub struct City {
|
||||
mut:
|
||||
name string
|
||||
}
|
||||
|
||||
pub struct Country {
|
||||
mut:
|
||||
cities []City
|
||||
name string
|
||||
}
|
||||
|
||||
struct Data {
|
||||
mut:
|
||||
countries []Country
|
||||
users map[string]User
|
||||
extra map[string]map[string]int
|
||||
}
|
||||
|
||||
fn test_nested_type() {
|
||||
data_expected := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}'
|
||||
data := Data{
|
||||
countries: [
|
||||
Country{
|
||||
name: 'UK'
|
||||
cities: [City{'London'}, City{'Manchester'}]
|
||||
},
|
||||
Country{
|
||||
name: 'KU'
|
||||
cities: [City{'Donlon'}, City{'Termanches'}]
|
||||
},
|
||||
]
|
||||
users: {
|
||||
'Foo': User{
|
||||
age: 10
|
||||
nums: [1, 2, 3]
|
||||
last_name: 'Johnson'
|
||||
is_registered: true
|
||||
typ: 0
|
||||
pets: 'little foo'
|
||||
}
|
||||
'Boo': User{
|
||||
age: 20
|
||||
nums: [5, 3, 1]
|
||||
last_name: 'Smith'
|
||||
is_registered: false
|
||||
typ: 4
|
||||
pets: 'little boo'
|
||||
}
|
||||
}
|
||||
extra: {
|
||||
'2': {
|
||||
'n1': 2
|
||||
'n2': 4
|
||||
'n3': 8
|
||||
'n4': 16
|
||||
}
|
||||
'3': {
|
||||
'n1': 3
|
||||
'n2': 9
|
||||
'n3': 27
|
||||
'n4': 81
|
||||
}
|
||||
}
|
||||
}
|
||||
out := json.encode(data)
|
||||
assert out == data_expected
|
||||
}
|
||||
|
||||
struct Human {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Item {
|
||||
tag string
|
||||
}
|
||||
|
||||
enum Animal {
|
||||
dog // Will be encoded as `0`
|
||||
cat
|
||||
}
|
||||
|
||||
type Entity = Animal | Human | Item | string | time.Time
|
||||
|
||||
struct SomeGame {
|
||||
title string
|
||||
player Entity
|
||||
other []Entity
|
||||
}
|
||||
|
||||
fn test_encode_decode_sumtype() {
|
||||
t := time.now()
|
||||
game := SomeGame{
|
||||
title: 'Super Mega Game'
|
||||
player: Human{'Monke'}
|
||||
other: [
|
||||
Entity(Item{'Pen'}),
|
||||
Item{'Cookie'},
|
||||
Animal.cat,
|
||||
'Stool',
|
||||
t,
|
||||
]
|
||||
}
|
||||
|
||||
enc := json.encode(game, enum_as_int: true)
|
||||
|
||||
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},1,"Stool",{"_type":"${typeof(time.Time{}).name.all_after_last('.')}","value":${t.unix()}}]}'
|
||||
|
||||
dec := json.decode[SomeGame](enc)!
|
||||
|
||||
assert game.title == dec.title
|
||||
assert game.player == dec.player
|
||||
assert (game.other[2] as Animal) == .cat
|
||||
assert dec.other[2] == Entity(Animal.cat)
|
||||
assert (game.other[4] as time.Time).unix() == (dec.other[4] as time.Time).unix()
|
||||
}
|
||||
|
||||
struct Foo3 {
|
||||
name string
|
||||
age int @[omitempty]
|
||||
}
|
||||
|
||||
// fn test_omit_empty() {
|
||||
// foo := Foo3{'Bob', 0}
|
||||
// assert json.encode(foo) == '{"name":"Bob"}'
|
||||
// assert json.encode_pretty(foo) == '{
|
||||
// "name": "Bob"
|
||||
// }'
|
||||
// }
|
||||
|
||||
struct Foo31 {
|
||||
name string
|
||||
age ?int
|
||||
}
|
||||
|
||||
fn test_option_instead_of_omit_empty() {
|
||||
foo := Foo31{
|
||||
name: 'Bob'
|
||||
}
|
||||
assert json.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"name": "Bob"
|
||||
}'
|
||||
}
|
||||
|
||||
struct Asdasd {
|
||||
data GamePacketData
|
||||
}
|
||||
|
||||
type GamePacketData = GPEquipItem | GPScale
|
||||
|
||||
struct GPScale {
|
||||
value f32
|
||||
}
|
||||
|
||||
struct GPEquipItem {
|
||||
name string
|
||||
}
|
||||
|
||||
fn create_game_packet(data &GamePacketData) string {
|
||||
return json.encode(data)
|
||||
}
|
||||
|
||||
fn test_encode_sumtype_defined_ahead() {
|
||||
ret := create_game_packet(&GamePacketData(GPScale{}))
|
||||
assert ret == '{"value":0,"_type":"GPScale"}'
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
import x.json2 as json
|
||||
import time
|
||||
|
||||
enum JobTitle {
|
||||
manager
|
||||
executive
|
||||
worker
|
||||
}
|
||||
|
||||
pub struct Employee {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
}
|
||||
|
||||
const currency_id = 'cconst'
|
||||
|
||||
pub struct Price {
|
||||
net f64
|
||||
currency_id string = currency_id @[json: currencyId]
|
||||
}
|
||||
|
||||
//! BUGFIX
|
||||
fn test_field_with_default_expr() {
|
||||
data := '[{"net":1},{"net":2,"currencyId":"cjson"}]'
|
||||
prices := json.decode[[]Price](data)!
|
||||
assert prices == [Price{
|
||||
net: 1
|
||||
currency_id: 'cconst'
|
||||
}, Price{
|
||||
net: 2
|
||||
currency_id: 'cjson'
|
||||
}]
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_decode_top_level_array() {
|
||||
s := '[{"name":"Peter", "age": 29}, {"name":"Bob", "age":31}]'
|
||||
x := json.decode[[]Employee](s) or { panic(err) }
|
||||
assert x.len == 2
|
||||
assert x[0].name == 'Peter'
|
||||
assert x[0].age == 29
|
||||
assert x[1].name == 'Bob'
|
||||
assert x[1].age == 31
|
||||
}
|
||||
|
||||
struct Human {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Item {
|
||||
tag string
|
||||
}
|
||||
|
||||
enum Animal {
|
||||
dog
|
||||
cat
|
||||
}
|
||||
|
||||
type Entity = Animal | Human | Item | string | time.Time
|
||||
|
||||
struct SomeGame {
|
||||
title string
|
||||
player Entity
|
||||
other []Entity
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_encode_decode_sumtype() {
|
||||
enc := '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},1,"Stool",{"_type":"Time","value":${t.unix()}}]}'
|
||||
|
||||
dec := json.decode[SomeGame](enc)!
|
||||
|
||||
assert game.title == dec.title
|
||||
assert game.player == dec.player
|
||||
assert (game.other[2] as Animal) == (dec.other[2] as Animal)
|
||||
assert (game.other[4] as time.Time).unix() == (dec.other[4] as time.Time).unix()
|
||||
}
|
||||
|
||||
struct User2 {
|
||||
mut:
|
||||
age int
|
||||
nums []int
|
||||
reg_date time.Time
|
||||
}
|
||||
|
||||
// User struct needs to be `pub mut` for now in order to access and manipulate values
|
||||
struct User {
|
||||
pub mut:
|
||||
age int
|
||||
nums []int
|
||||
last_name string @[json: lastName]
|
||||
is_registered bool @[json: IsRegistered]
|
||||
typ int @[json: 'type']
|
||||
pets string @[json: 'pet_animals'; raw]
|
||||
}
|
||||
|
||||
fn test_parse_user() {
|
||||
s := '{"age": 10, "nums": [1,2,3], "type": 1, "lastName": "Johnson", "IsRegistered": true, "pet_animals": {"name": "Bob", "animal": "Dog"}}'
|
||||
u2 := json.decode[User2](s)!
|
||||
u := json.decode[User](s)!
|
||||
assert u.age == 10
|
||||
assert u.last_name == 'Johnson'
|
||||
assert u.is_registered == true
|
||||
assert u.nums.len == 3
|
||||
assert u.nums[0] == 1
|
||||
assert u.nums[1] == 2
|
||||
assert u.nums[2] == 3
|
||||
assert u.typ == 1
|
||||
assert u.pets == '{"name":"Bob","animal":"Dog"}'
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_encode_decode_time() {
|
||||
user := User2{
|
||||
age: 25
|
||||
reg_date: time.new(year: 2020, month: 12, day: 22, hour: 7, minute: 23)
|
||||
}
|
||||
s := json.encode(user)
|
||||
assert s.contains('"reg_date":1608621780')
|
||||
user2 := json.decode[User2](s)!
|
||||
assert user2.reg_date.str() == '2020-12-22 07:23:00'
|
||||
}
|
||||
|
||||
struct City {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Country {
|
||||
cities []City
|
||||
name string
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_struct_in_struct() {
|
||||
country := json.decode[Country]('{ "name": "UK", "cities": [{"name":"London"}, {"name":"Manchester"}]}')!
|
||||
assert country.name == 'UK'
|
||||
assert country.cities.len == 2
|
||||
assert country.cities[0].name == 'London'
|
||||
assert country.cities[1].name == 'Manchester'
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_parse_map() {
|
||||
expected := {
|
||||
'one': 1
|
||||
'two': 2
|
||||
'three': 3
|
||||
'four': 4
|
||||
}
|
||||
out := json.decode[map[string]int]('{"one":1,"two":2,"three":3,"four":4}')!
|
||||
assert out == expected
|
||||
}
|
||||
|
||||
struct Data {
|
||||
countries []Country
|
||||
users map[string]User
|
||||
extra map[string]map[string]int
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_nested_type() {
|
||||
data_expected := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}'
|
||||
data := Data{
|
||||
countries: [
|
||||
Country{
|
||||
name: 'UK'
|
||||
cities: [City{'London'}, City{'Manchester'}]
|
||||
},
|
||||
Country{
|
||||
name: 'KU'
|
||||
cities: [City{'Donlon'}, City{'Termanches'}]
|
||||
},
|
||||
]
|
||||
users: {
|
||||
'Foo': User{
|
||||
age: 10
|
||||
nums: [1, 2, 3]
|
||||
last_name: 'Johnson'
|
||||
is_registered: true
|
||||
typ: 0
|
||||
pets: 'little foo'
|
||||
}
|
||||
'Boo': User{
|
||||
age: 20
|
||||
nums: [5, 3, 1]
|
||||
last_name: 'Smith'
|
||||
is_registered: false
|
||||
typ: 4
|
||||
pets: 'little boo'
|
||||
}
|
||||
}
|
||||
extra: {
|
||||
'2': {
|
||||
'n1': 2
|
||||
'n2': 4
|
||||
'n3': 8
|
||||
'n4': 16
|
||||
}
|
||||
'3': {
|
||||
'n1': 3
|
||||
'n2': 9
|
||||
'n3': 27
|
||||
'n4': 81
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data2 := json.decode[Data](data_expected)!
|
||||
assert data2.countries.len == data.countries.len
|
||||
for i in 0 .. 1 {
|
||||
assert data2.countries[i].name == data.countries[i].name
|
||||
assert data2.countries[i].cities.len == data.countries[i].cities.len
|
||||
for j in 0 .. 1 {
|
||||
assert data2.countries[i].cities[j].name == data.countries[i].cities[j].name
|
||||
}
|
||||
}
|
||||
for key, user in data.users {
|
||||
assert data2.users[key].age == user.age
|
||||
assert data2.users[key].nums == user.nums
|
||||
assert data2.users[key].last_name == user.last_name
|
||||
assert data2.users[key].is_registered == user.is_registered
|
||||
assert data2.users[key].typ == user.typ
|
||||
//!assert data2.users[key].pets == user.pets TODO FIX
|
||||
}
|
||||
for k, v in data.extra {
|
||||
for k2, v2 in v {
|
||||
assert data2.extra[k][k2] == v2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_errors() {
|
||||
invalid_array := fn () {
|
||||
data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":{"name":"Donlon"},"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}'
|
||||
json.decode[Data](data) or {
|
||||
assert err.msg().starts_with('Json element is not an array:')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
invalid_object := fn () {
|
||||
data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":[{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}],"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}'
|
||||
json.decode[Data](data) or {
|
||||
assert err.msg().starts_with('Json element is not an object:')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
invalid_array()
|
||||
invalid_object()
|
||||
}
|
||||
|
||||
type ID = string
|
||||
type GG = int
|
||||
|
||||
struct Message {
|
||||
id ID
|
||||
ij GG
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_decode_alias_struct() {
|
||||
msg := json.decode[Message]('{"id": "118499178790780929"}')!
|
||||
// hacky way of comparing aliased strings
|
||||
assert msg.id.str() == '118499178790780929'
|
||||
}
|
||||
|
||||
struct List {
|
||||
id int
|
||||
items []string
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_list() {
|
||||
list := json.decode[List]('{"id": 1, "items": ["1", "2"]}')!
|
||||
assert list.id == 1
|
||||
assert list.items == ['1', '2']
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_list_no_id() {
|
||||
list := json.decode[List]('{"items": ["1", "2"]}')!
|
||||
assert list.id == 0
|
||||
assert list.items == ['1', '2']
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_list_no_items() {
|
||||
list := json.decode[List]('{"id": 1}')!
|
||||
assert list.id == 1
|
||||
assert list.items == []
|
||||
}
|
||||
|
||||
struct Info {
|
||||
id int
|
||||
items []string
|
||||
maps map[string]string
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_decode_null_object() {
|
||||
info := json.decode[Info]('{"id": 22, "items": null, "maps": null}')!
|
||||
assert info.id == 22
|
||||
assert '${info.items}' == '[]'
|
||||
assert '${info.maps}' == '{}'
|
||||
}
|
||||
|
||||
//! BUGFIX - .from_json(res)
|
||||
fn test_decode_missing_maps_field() {
|
||||
info := json.decode[Info]('{"id": 22, "items": null}')!
|
||||
assert info.id == 22
|
||||
assert '${info.items}' == '[]'
|
||||
assert '${info.maps}' == '{}'
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
struct Response[T] {
|
||||
code int
|
||||
msg string
|
||||
data ?T
|
||||
}
|
||||
|
||||
struct UserInfo {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
fn g[T](x T) string {
|
||||
return 'abc'
|
||||
}
|
||||
|
||||
fn response_to_string[T](response Response[T]) string {
|
||||
return g(response)
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
res := response_to_string(Response{1, 'success', ?UserInfo{'Jay Chou', 46}})
|
||||
assert res == 'abc'
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import x.json2 as json
|
||||
|
||||
type Prices = Price | []Price
|
||||
|
||||
pub struct ShopResponseData {
|
||||
attributes Attributes
|
||||
}
|
||||
|
||||
struct Attributes {
|
||||
price ?Prices
|
||||
}
|
||||
|
||||
struct Price {
|
||||
net f64
|
||||
}
|
||||
|
||||
pub type Animal = Cat | Dog
|
||||
|
||||
pub struct Cat {
|
||||
cat_name string
|
||||
}
|
||||
|
||||
pub struct Dog {
|
||||
dog_name string
|
||||
}
|
||||
|
||||
type Sum = int | string | bool | []string
|
||||
|
||||
type StructSumTypes = Stru | Stru2
|
||||
|
||||
type Mixed = Cat | int | map[string]int
|
||||
|
||||
type Maybes = ?int | ?string
|
||||
|
||||
type MultiArray = []int
|
||||
| []bool
|
||||
| [][]string
|
||||
| []map[string]map[string][]int
|
||||
| map[string]json.Any
|
||||
| string
|
||||
|
||||
type StructLists = Cat | []Cat | map[string]Dog
|
||||
|
||||
type SumAlias = Sum
|
||||
|
||||
struct AliasWrap {
|
||||
val SumAlias
|
||||
}
|
||||
|
||||
struct Empty1 {
|
||||
a ?string @[omitempty]
|
||||
}
|
||||
|
||||
type Elem1 = int | Empty1
|
||||
|
||||
pub struct Stru {
|
||||
val int
|
||||
val2 string
|
||||
val3 Stru2
|
||||
val4 int
|
||||
val5 int
|
||||
}
|
||||
|
||||
pub struct Stru2 {
|
||||
a int
|
||||
steak string
|
||||
}
|
||||
|
||||
type NewAny = int | string | bool | []NewAny | map[string]NewAny | ?int
|
||||
|
||||
fn test_simple_sum_type() {
|
||||
assert json.decode[Sum]('1')! == Sum(1)
|
||||
|
||||
assert json.decode[Sum]('"hello"')! == Sum('hello')
|
||||
|
||||
assert json.decode[Sum]('true')! == Sum(true)
|
||||
assert json.decode[Sum]('false')! == Sum(false)
|
||||
|
||||
assert json.decode[Sum]('["1", "2", "3"]')! == Sum(['1', '2', '3'])
|
||||
}
|
||||
|
||||
fn test_any_sum_type() {
|
||||
assert json.decode[json.Any]('1')! == json.Any(f64(1))
|
||||
assert json.decode[json.Any]('123321')! == json.Any(f64(123321))
|
||||
|
||||
assert json.decode[json.Any]('"hello"')! == json.Any('hello')
|
||||
|
||||
assert json.decode[json.Any]('true')! == json.Any(true)
|
||||
assert json.decode[json.Any]('false')! == json.Any(false)
|
||||
|
||||
assert json.decode[json.Any]('1.1')! == json.Any(f64(1.1))
|
||||
|
||||
assert json.decode[[]json.Any]('["1", "2", "3"]')! == [json.Any('1'), json.Any('2'), json.Any('3')]
|
||||
assert json.decode[json.Any]('["1", "2", "3"]')! == json.Any([json.Any('1'), json.Any('2'),
|
||||
json.Any('3')])
|
||||
|
||||
assert json.decode[[]json.Any]('[true, false, true]')! == [json.Any(true), json.Any(false),
|
||||
json.Any(true)]
|
||||
assert json.decode[json.Any]('[true, false, true]')! == json.Any([json.Any(true), json.Any(false),
|
||||
json.Any(true)])
|
||||
|
||||
assert json.decode[json.Any]('{"hello": "world"}')! == json.Any({
|
||||
'hello': json.Any('world')
|
||||
})
|
||||
|
||||
assert json.decode[map[string]json.Any]('{"hello": "world"}')! == {
|
||||
'hello': json.Any('world')
|
||||
}
|
||||
|
||||
assert json.decode[json.Any]('{"hello1": {"hello2": "world"}}')! == json.Any({
|
||||
'hello1': json.Any({
|
||||
'hello2': json.Any('world')
|
||||
})
|
||||
})
|
||||
|
||||
assert json.decode[NewAny]('{"name": null, "value": "hi"}')! == NewAny({
|
||||
'name': NewAny(?int(none))
|
||||
'value': NewAny('hi')
|
||||
})
|
||||
|
||||
assert json.decode[json.Any]('[]')! == json.Any([]json.Any{})
|
||||
assert json.decode[json.Any]('{}')! == json.Any(map[string]json.Any{})
|
||||
}
|
||||
|
||||
fn test_sum_type_struct() {
|
||||
if x := json.decode[Animal]('{"cat_name": "Tom"}') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[Animal]('{"dog_name": "Rex"}') {
|
||||
assert false
|
||||
}
|
||||
assert json.decode[Animal]('{"dog_name": "Rex", "_type": "Dog"}')! == Animal(Dog{'Rex'})
|
||||
|
||||
// struct sumtype in random order
|
||||
assert json.decode[StructSumTypes]('{"_type": "Stru", "val": 1, "val2": "lala", "val3": {"a": 2, "steak": "leleu"}, "val4": 2147483000, "val5": 2147483000}')! == StructSumTypes(Stru{1, 'lala', Stru2{2, 'leleu'}, 2147483000, 2147483000})
|
||||
}
|
||||
|
||||
fn test_sum_type_array_with_single_struct_variant() {
|
||||
assert json.decode[Elem1]('{"a":"b"}')! == Elem1(Empty1{
|
||||
a: 'b'
|
||||
})
|
||||
|
||||
assert json.decode[[]Elem1]('[0, {"a":"b"}, 2]')! == [Elem1(0), Elem1(Empty1{
|
||||
a: 'b'
|
||||
}),
|
||||
Elem1(2)]
|
||||
}
|
||||
|
||||
fn test_sum_type_mixed() {
|
||||
assert json.decode[Mixed]('{"key":0}')! == Mixed({
|
||||
'key': 0
|
||||
})
|
||||
assert json.decode[Mixed]('10')! == Mixed(10)
|
||||
}
|
||||
|
||||
// to be implemented
|
||||
fn test_sum_type_options_fail() {
|
||||
assert json.decode[Maybes]('null')! == Maybes(?int(none))
|
||||
if x := json.decode[Maybes]('99') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[Maybes]('hi') {
|
||||
assert false
|
||||
}
|
||||
if x := json.decode[Maybes]('true') {
|
||||
assert false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_sum_type_alias() {
|
||||
assert json.decode[SumAlias]('99')! == SumAlias(99)
|
||||
assert json.decode[SumAlias]('"hi"')! == SumAlias('hi')
|
||||
assert json.decode[SumAlias]('true')! == SumAlias(true)
|
||||
assert json.decode[SumAlias]('["hi", "bye"]')! == SumAlias(['hi', 'bye'])
|
||||
|
||||
assert json.decode[AliasWrap]('{"val": 99}')! == AliasWrap{
|
||||
val: SumAlias(99)
|
||||
}
|
||||
assert json.decode[[]SumAlias]('[99, "hi", true, ["bye"]]')! == [SumAlias(99), SumAlias('hi'),
|
||||
SumAlias(true), SumAlias(['bye'])]
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import x.json2 as json
|
||||
|
||||
type Prices = Price | []Price
|
||||
|
||||
pub struct ShopResponseData {
|
||||
attributes Attributes
|
||||
}
|
||||
|
||||
struct Attributes {
|
||||
price ?Prices
|
||||
}
|
||||
|
||||
struct Price {
|
||||
net f64
|
||||
}
|
||||
|
||||
type Animal = Cat | Dog
|
||||
|
||||
struct Cat {
|
||||
cat_name string
|
||||
}
|
||||
|
||||
struct Dog {
|
||||
dog_name string
|
||||
}
|
||||
|
||||
type Sum = int | string | bool
|
||||
|
||||
fn test_main() {
|
||||
data := '{"attributes": {"price": [{"net": 1, "_type": "Price"}, {"net": 2, "_type": "Price"}]}}'
|
||||
entity := json.decode[ShopResponseData](data) or { panic(err) }
|
||||
assert entity == ShopResponseData{
|
||||
attributes: Attributes{
|
||||
price: Prices([Price{
|
||||
net: 1
|
||||
}, Price{
|
||||
net: 2
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
data2 := '{"attributes": {"price": {"net": 1, "_type": "Price"}}}'
|
||||
entity2 := json.decode[ShopResponseData](data2) or { panic(err) }
|
||||
assert entity2 == ShopResponseData{
|
||||
attributes: Attributes{
|
||||
price: Prices(Price{
|
||||
net: 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
data3 := json.encode(ShopResponseData{
|
||||
attributes: Attributes{
|
||||
price: Prices([Price{
|
||||
net: 1.2
|
||||
}])
|
||||
}
|
||||
})
|
||||
assert data3 == '{"attributes":{"price":[{"net":1.2,"_type":"Price"}]}}'
|
||||
|
||||
entity3 := json.decode[ShopResponseData](data3) or { panic(err) }
|
||||
assert entity3 == ShopResponseData{
|
||||
attributes: Attributes{
|
||||
price: Prices([Price{
|
||||
net: 1.2
|
||||
}])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_sum_types() {
|
||||
data1 := json.encode(Animal(Dog{
|
||||
dog_name: 'Caramelo'
|
||||
}))
|
||||
assert data1 == '{"dog_name":"Caramelo","_type":"Dog"}'
|
||||
|
||||
s := '{"_type":"Cat","cat_name":"Whiskers"}'
|
||||
animal := json.decode[Animal](s) or {
|
||||
println(err)
|
||||
assert false
|
||||
return
|
||||
}
|
||||
|
||||
assert animal is Cat
|
||||
if animal is Cat {
|
||||
assert animal.cat_name == 'Whiskers'
|
||||
} else {
|
||||
assert false, 'Wrong sumtype decode. In this case animal is a Cat'
|
||||
}
|
||||
|
||||
s2 := '[{"_type":"Cat","cat_name":"Whiskers"}, {"_type":"Dog","dog_name":"Goofie"}]'
|
||||
|
||||
animals := json.decode[[]Animal](s2) or {
|
||||
println(err)
|
||||
assert false
|
||||
return
|
||||
}
|
||||
|
||||
assert animals.len == 2
|
||||
assert animals[0] is Cat
|
||||
assert animals[1] is Dog
|
||||
cat := animals[0] as Cat
|
||||
dog := animals[1] as Dog
|
||||
assert cat.cat_name == 'Whiskers'
|
||||
assert dog.dog_name == 'Goofie'
|
||||
|
||||
j := json.encode(animals[0])
|
||||
assert j == '{"cat_name":"Whiskers","_type":"Cat"}'
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import x.json2 as json
|
||||
|
||||
struct Bar {
|
||||
b []int = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}
|
||||
|
||||
struct Foo {
|
||||
Bar
|
||||
a []int = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
str := json.encode(Foo{})
|
||||
assert json.decode[Foo](str)!.str() == 'Foo{
|
||||
Bar: Bar{
|
||||
b: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}
|
||||
a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}'
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import io
|
||||
import x.json2 as json
|
||||
|
||||
struct ChunkedReader {
|
||||
data []u8
|
||||
chunk_size int
|
||||
mut:
|
||||
pos int
|
||||
}
|
||||
|
||||
fn (mut r ChunkedReader) read(mut buf []u8) !int {
|
||||
if r.pos >= r.data.len {
|
||||
return io.Eof{}
|
||||
}
|
||||
mut n := r.chunk_size
|
||||
remaining := r.data.len - r.pos
|
||||
if n > remaining {
|
||||
n = remaining
|
||||
}
|
||||
if n > buf.len {
|
||||
n = buf.len
|
||||
}
|
||||
read := copy(mut buf[..n], r.data[r.pos..r.pos + n])
|
||||
r.pos += read
|
||||
return read
|
||||
}
|
||||
|
||||
fn test_public_scanner_iterates_tokens() {
|
||||
mut scanner := json.new_scanner('{"items":[1,true,null,"x"]}')
|
||||
mut got := []string{}
|
||||
for {
|
||||
token := scanner.next()!
|
||||
got << '${token.kind}:${token.literal()}'
|
||||
if token.is_eof() {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert got == [
|
||||
'lcbr:',
|
||||
'str:items',
|
||||
'colon:',
|
||||
'lsbr:',
|
||||
'int:1',
|
||||
'comma:',
|
||||
'bool:true',
|
||||
'comma:',
|
||||
'null:null',
|
||||
'comma:',
|
||||
'str:x',
|
||||
'rsbr:',
|
||||
'rcbr:',
|
||||
'eof:',
|
||||
]
|
||||
}
|
||||
|
||||
fn test_reader_scanner_iterates_tokens() {
|
||||
mut reader := ChunkedReader{
|
||||
data: '[10,true,"chunked",null]'.bytes()
|
||||
chunk_size: 2
|
||||
}
|
||||
mut scanner := json.new_reader_scanner(reader: reader, buffer_size: 3)
|
||||
defer {
|
||||
scanner.free()
|
||||
}
|
||||
mut got := []string{}
|
||||
for {
|
||||
token := scanner.next()!
|
||||
got << '${token.kind}:${token.literal()}'
|
||||
if token.is_eof() {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert got == [
|
||||
'lsbr:',
|
||||
'int:10',
|
||||
'comma:',
|
||||
'bool:true',
|
||||
'comma:',
|
||||
'str:chunked',
|
||||
'comma:',
|
||||
'null:null',
|
||||
'rsbr:',
|
||||
'eof:',
|
||||
]
|
||||
}
|
||||
|
||||
fn test_reader_scanner_reports_errors() {
|
||||
mut reader := ChunkedReader{
|
||||
data: r'["\z"]'.bytes()
|
||||
chunk_size: 1
|
||||
}
|
||||
mut scanner := json.new_reader_scanner(reader: reader, buffer_size: 1)
|
||||
defer {
|
||||
scanner.free()
|
||||
}
|
||||
assert scanner.next()!.kind == .lsbr
|
||||
scanner.next() or {
|
||||
assert err.msg().contains('invalid backslash escape')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import x.json2
|
||||
|
||||
struct ScientificI64Value {
|
||||
size i64
|
||||
}
|
||||
|
||||
fn test_decode_i64_from_scientific_notation_number() {
|
||||
decoded := json2.decode[ScientificI64Value]('{"size":1.7596215426069998e+12}')!
|
||||
assert decoded.size == i64(1.7596215426069998e+12)
|
||||
}
|
||||
|
||||
fn test_decode_i64_from_scientific_notation_string() {
|
||||
decoded := json2.decode[ScientificI64Value]('{"size":"1.7596215426069998e+12"}')!
|
||||
assert decoded.size == i64(1.7596215426069998e+12)
|
||||
}
|
||||
|
||||
fn test_decode_i64_from_plain_fraction_still_fails() {
|
||||
if _ := json2.decode[ScientificI64Value]('{"size":123.45}') {
|
||||
assert false
|
||||
} else {
|
||||
assert true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
module json2
|
||||
|
||||
import time
|
||||
|
||||
@[markused]
|
||||
const any_time_type_marker = time.Time{}
|
||||
|
||||
// Any is a sum type that lists the possible types to be decoded and used.
|
||||
// `Any` priority order for numbers: floats -> signed integers -> unsigned integers
|
||||
// `Any` priority order for strings: string -> time.Time
|
||||
|
||||
@[markused]
|
||||
pub type Any = []Any
|
||||
| bool
|
||||
| f64
|
||||
| f32
|
||||
| i64
|
||||
| int
|
||||
| i32
|
||||
| i16
|
||||
| i8
|
||||
| map[string]Any
|
||||
| string
|
||||
| time.Time
|
||||
| u64
|
||||
| u32
|
||||
| u16
|
||||
| u8
|
||||
| Null
|
||||
|
||||
// Null is a simple representation of the `null` value in JSON.
|
||||
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
|
||||
@[markused]
|
||||
pub fn (mut n Null) from_json_null() {}
|
||||
|
||||
// to_json implements a custom encoder for json2
|
||||
pub fn (n Null) to_json() string {
|
||||
return 'null'
|
||||
}
|
||||
|
||||
// ValueKind enumerates the kinds of possible values of the Any sumtype.
|
||||
enum ValueKind {
|
||||
array
|
||||
object
|
||||
string
|
||||
number
|
||||
boolean
|
||||
null
|
||||
}
|
||||
Reference in new issue
Block a user