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

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