Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+232
View File
@@ -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() }
```
+54
View File
@@ -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
}
+321
View File
@@ -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 `}`')
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
module json2
const true_string = 'true'
const false_string = 'false'
const null_string = 'null'
+109
View File
@@ -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 // ""
}
+64
View File
@@ -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
+413
View File
@@ -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)!
}
+17
View File
@@ -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)
}
+916
View File
@@ -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) }
}
+43
View File
@@ -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)
}
+415
View File
@@ -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
}
}
+690
View File
@@ -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)
}
+374
View File
@@ -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
}
+196
View File
@@ -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
}
+48
View File
@@ -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']
}
}
+347
View File
@@ -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}'
}
+213
View File
@@ -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"\')!')
}
+184
View File
@@ -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
}
+121
View File
@@ -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'
}
+401
View File
@@ -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
}'
}
+206
View File
@@ -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')
}
+164
View File
@@ -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}
}
}
@@ -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
}
@@ -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
}
@@ -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'
}
@@ -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}'
}
@@ -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]
}'
}
+102
View File
@@ -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
}
}
+54
View File
@@ -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
}