x.json2.decode2: minor improvements and bugfixes (#23083)

This commit is contained in:
Hitalo Souza 2024-12-07 04:22:46 -04:00 committed by GitHub
parent de3b184b01
commit e32e9f70ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1329 additions and 81 deletions

View File

@ -12,6 +12,8 @@ const false_in_string = 'false'
const float_zero_in_string = '0.0'
const whitespace_chars = [` `, `\t`, `\n`]!
// Node represents a node in a linked list to store ValueInfo.
struct Node[T] {
mut:
@ -147,55 +149,6 @@ pub enum ValueKind {
null
}
// check_if_json_match checks if the JSON string matches the expected type T.
fn check_if_json_match[T](val string) ! {
// check if the JSON string is empty
if val == '' {
return error('empty string')
}
// check if generic type matches the JSON type
value_kind := get_value_kind(val[0])
$if T is $option {
// TODO
} $else $if T is $sumtype {
// TODO
} $else $if T is $alias {
// TODO
} $else $if T is $string {
if value_kind != .string_ {
return error('Expected string, but got ${value_kind}')
}
} $else $if T is time.Time {
if value_kind != .string_ {
return error('Expected string, but got ${value_kind}')
}
} $else $if T is $map {
if value_kind != .object {
return error('Expected object, but got ${value_kind}')
}
} $else $if T is $array {
if value_kind != .array {
return error('Expected array, but got ${value_kind}')
}
} $else $if T is $struct {
if value_kind != .object {
return error('Expected object, but got ${value_kind}')
}
} $else $if T in [$enum, $int, $float] {
if value_kind != .number {
return error('Expected number, but got ${value_kind}')
}
} $else $if T is bool {
if value_kind != .boolean {
return error('Expected boolean, but got ${value_kind}')
}
} $else {
return error('cannot decode value with ${value_kind} type')
}
}
// error generates an error message with context from the JSON string.
fn (mut checker Decoder) error(message string) ! {
json := if checker.json.len < checker.checker_idx + 5 {
@ -234,6 +187,14 @@ fn (mut checker Decoder) check_json_format(val string) ! {
return checker.error('empty string')
}
// skip whitespace
for val[checker.checker_idx] in whitespace_chars {
if checker.checker_idx >= checker_end - 1 {
break
}
checker.checker_idx++
}
// check if generic type matches the JSON type
value_kind := get_value_kind(val[checker.checker_idx])
start_idx_position := checker.checker_idx
@ -264,6 +225,9 @@ fn (mut checker Decoder) check_json_format(val string) ! {
checker.checker_idx += 3
}
.object {
if checker_end - checker.checker_idx < 2 {
return checker.error('EOF error: expecting a complete object after `{`')
}
checker.checker_idx++
for val[checker.checker_idx] != `}` {
// check if the JSON string is an empty object
@ -272,7 +236,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
}
// skip whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
if checker.checker_idx >= checker_end - 1 {
break
}
@ -294,7 +258,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
if checker.checker_idx >= checker_end - 1 {
return checker.error('EOF error: key colon not found')
}
if val[checker.checker_idx] !in [` `, `\t`, `\n`] {
if val[checker.checker_idx] !in whitespace_chars {
return checker.error('invalid value after object key')
}
checker.checker_idx++
@ -307,7 +271,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
checker.checker_idx++
// skip whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
@ -315,7 +279,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
`"`, `[`, `{`, `0`...`9`, `-`, `n`, `t`, `f` {
checker.check_json_format(val)!
// whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] == `}` {
@ -327,11 +291,11 @@ fn (mut checker Decoder) check_json_format(val string) ! {
if val[checker.checker_idx] == `,` {
checker.checker_idx++
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] != `"` {
return checker.error('Expecting object key')
return checker.error('Expecting object key after `,`')
}
} else {
if val[checker.checker_idx] == `}` {
@ -360,7 +324,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
for val[checker.checker_idx] != `]` {
// skip whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
if checker.checker_idx >= checker_end - 1 {
break
}
@ -378,7 +342,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
checker.check_json_format(val)!
// whitespace
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] == `]` {
@ -390,7 +354,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
if val[checker.checker_idx] == `,` {
checker.checker_idx++
for val[checker.checker_idx] in [` `, `\t`, `\n`] {
for val[checker.checker_idx] in whitespace_chars {
checker.checker_idx++
}
if val[checker.checker_idx] == `]` {
@ -548,7 +512,7 @@ fn (mut checker Decoder) check_json_format(val string) ! {
for checker.checker_idx < checker_end - 1 && val[checker.checker_idx] !in [`,`, `:`, `}`, `]`] {
// get trash characters after the value
if val[checker.checker_idx] !in [` `, `\t`, `\n`] {
if val[checker.checker_idx] !in whitespace_chars {
checker.error('invalid value. Unexpected character after ${value_kind} end')!
} else {
// whitespace
@ -558,17 +522,23 @@ fn (mut checker Decoder) check_json_format(val string) ! {
}
// decode decodes a JSON string into a specified type.
@[manualfree]
pub fn decode[T](val string) !T {
if val == '' {
return error('empty string')
}
mut decoder := Decoder{
json: val
}
decoder.check_json_format(val)!
check_if_json_match[T](val)!
mut result := T{}
decoder.current_node = decoder.values_info.head
decoder.decode_value(mut &result)!
unsafe {
decoder.values_info.free()
}
return result
}
@ -629,6 +599,8 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
}
val = string_buffer.bytestr()
} else {
return error('Expected string, but got ${string_info.value_kind}')
}
} $else $if T.unaliased_typ is $sumtype {
decoder.decode_sumtype(mut val)!
@ -852,6 +824,8 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
}
current_field_info = current_field_info.next
}
} else {
return error('Expected object, but got ${struct_info.value_kind}')
}
unsafe {
struct_fields_info.free()
@ -860,6 +834,10 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
} $else $if T.unaliased_typ is bool {
value_info := decoder.current_node.value
if value_info.value_kind != .boolean {
return error('Expected boolean, but got ${value_info.value_kind}')
}
unsafe {
val = vmemcmp(decoder.json.str + value_info.position, true_in_string.str,
true_in_string.len) == 0
@ -873,6 +851,8 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! {
unsafe {
string_buffer_to_generic_number(val, bytes)
}
} else {
return error('Expected number, but got ${value_info.value_kind}')
}
} $else {
return error('cannot decode value with ${typeof(val).name} type')
@ -904,6 +884,8 @@ fn (mut decoder Decoder) decode_array[T](mut val []T) ! {
val << array_element
}
} else {
return error('Expected array, but got ${array_info.value_kind}')
}
}
@ -946,6 +928,8 @@ fn (mut decoder Decoder) decode_map[K, V](mut val map[K]V) ! {
}
decoder.decode_value(mut val[key])!
}
} else {
return error('Expected object, but got ${map_info.value_kind}')
}
}

View File

@ -6,11 +6,8 @@ fn (mut decoder Decoder) get_decoded_sumtype_workaround[T](initialized_sumtype T
$if initialized_sumtype is $sumtype {
$for v in initialized_sumtype.variants {
if initialized_sumtype is v {
$if v is $array {
mut val := initialized_sumtype.clone()
decoder.decode_value(mut val)!
return T(val)
} $else {
// workaround for auto generated function considering sumtype as array
unsafe {
mut val := initialized_sumtype
decoder.decode_value(mut val)!
return T(val)

View File

@ -4,42 +4,42 @@ fn test_check_if_json_match() {
// /* Test wrong string values */
mut has_error := false
check_if_json_match[string]('{"key": "value"}') or {
decode[string]('{"key": "value"}') or {
assert err.str() == 'Expected string, but got object'
has_error = true
}
assert has_error, 'Expected error'
has_error = false
check_if_json_match[map[string]string]('"value"') or {
decode[map[string]string]('"value"') or {
assert err.str() == 'Expected object, but got string_'
has_error = true
}
assert has_error, 'Expected error'
has_error = false
check_if_json_match[[]int]('{"key": "value"}') or {
decode[[]int]('{"key": "value"}') or {
assert err.str() == 'Expected array, but got object'
has_error = true
}
assert has_error, 'Expected error'
has_error = false
check_if_json_match[string]('[1, 2, 3]') or {
decode[string]('[1, 2, 3]') or {
assert err.str() == 'Expected string, but got array'
has_error = true
}
assert has_error, 'Expected error'
has_error = false
check_if_json_match[int]('{"key": "value"}') or {
decode[int]('{"key": "value"}') or {
assert err.str() == 'Expected number, but got object'
has_error = true
}
assert has_error, 'Expected error'
has_error = false
check_if_json_match[bool]('{"key": "value"}') or {
decode[bool]('{"key": "value"}') or {
assert err.str() == 'Expected boolean, but got object'
has_error = true
}
@ -47,19 +47,19 @@ fn test_check_if_json_match() {
has_error = false
// /* Right string values */
check_if_json_match[string]('"value"') or { assert false }
decode[string]('"value"') or { assert false }
check_if_json_match[map[string]string]('{"key": "value"}') or { assert false }
decode[map[string]string]('{"key": "value"}') or { assert false }
check_if_json_match[[]int]('[1, 2, 3]') or { assert false }
decode[[]int]('[1, 2, 3]') or { assert false }
check_if_json_match[string]('"string"') or { assert false }
decode[string]('"string"') or { assert false }
check_if_json_match[int]('123') or { assert false }
decode[int]('123') or { assert false }
check_if_json_match[bool]('true') or { assert false }
decode[bool]('true') or { assert false }
check_if_json_match[bool]('false') or { assert false }
decode[bool]('false') or { assert false }
// TODO: test null
}
@ -157,7 +157,7 @@ fn test_check_json_format() {
},
{
'json': '{"key": 123, "key2": 456,}'
'error': '\n{"key": 123, "key2": 456,}\n ^ Expecting object key'
'error': '\n{"key": 123, "key2": 456,}\n ^ Expecting object key after `,`'
},
{
'json': '[[1, 2, 3], [4, 5, 6],]'

View File

@ -0,0 +1,29 @@
import x.json2.decoder2 as json
import x.json2
struct AnyStruct[T] {
val T
}
struct OptAnyStruct[T] {
val ?T
}
// struct OptAnyArrStruct {
// val []?json2.Any
// }
fn test_values() {
assert json.decode[AnyStruct[json2.Any]]('{"val":5}')!.val.int() == 5
assert json.decode[OptAnyStruct[json2.Any]]('{}')!.val == none
assert json.decode[AnyStruct[[]json2.Any]]('{"val":[5,10]}')!.val.map(it.int()) == [
5,
10,
]
// assert json.decode[OptAnyArrStruct]('{"val":[5,null,10]}')!.val == [?json2.Any(5),json.Null{},10] // skipped because test still fails even though they're the same
assert json2.encode[AnyStruct[json2.Any]](AnyStruct[json2.Any]{json2.Any(5)}) == '{"val":5}'
assert json2.encode[OptAnyStruct[json2.Any]](OptAnyStruct[json2.Any]{none}) == '{}'
assert json2.encode[AnyStruct[[]json2.Any]](AnyStruct[[]json2.Any]{[json2.Any(5), 10]}) == '{"val":[5,10]}'
// assert json2.encode[OptAnyArrStruct](OptAnyArrStruct{[?json2.Any(5),none,10]}) == '{"val":[5,null,10]}' // encode_array has not implemented optional arrays yet
}

View File

@ -0,0 +1,58 @@
import x.json2.decoder2 as 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
}

View File

@ -0,0 +1,212 @@
import x.json2.decoder2 as json
import x.json2
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
}
}

View File

@ -0,0 +1,74 @@
import x.json2.decoder2 as json
import x.json2
fn test_raw_decode_string() {
str := json.decode[json2.Any]('"Hello!"')!
assert str.str() == 'Hello!'
}
fn test_raw_decode_string_escape() {
jstr := json.decode[json2.Any]('"\u001b"')!
str := jstr.str()
assert str.len == 1
assert str[0] == 27
}
fn test_raw_decode_number() {
num := json.decode[json2.Any]('123')!
assert num.int() == 123
}
fn test_raw_decode_array() {
raw_arr := json.decode[json2.Any]('["Foo", 1]')!
arr := raw_arr.arr()
assert arr[0] or { 0 }.str() == 'Foo'
assert arr[1] or { 0 }.int() == 1
}
fn test_raw_decode_bool() {
bol := json.decode[json2.Any]('false')!
assert bol.bool() == false
}
fn test_raw_decode_map() {
raw_mp := json.decode[json2.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[json2.Any](r'"Hello $world"')!
assert str.str() == r'Hello $world'
}
fn test_raw_decode_map_with_whitespaces() {
raw_mp := json.decode[json2.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_nested_array_object() {
mut parser := json2.new_parser(r'[[[[[],[],[]]]],{"Test":{}},[[]]]', false)
decoded := parser.decode()!
assert parser.n_level == 0
}
fn test_raw_decode_map_invalid() {
json.decode[json2.Any]('{"name","Bob","age":20}') or {
assert err.msg() == '\n{"name",\n ^ invalid value after object key'
return
}
assert false
}
fn test_raw_decode_array_invalid() {
json.decode[json2.Any]('["Foo", 1,}') or {
assert err.msg() == '\n["Foo", 1,}\n ^ EOF error: array not closed'
return
}
assert false
}

View File

@ -0,0 +1,226 @@
import x.json2.decoder2 as json
import x.json2
import strings
import time
struct StructType[T] {
mut:
val T
}
fn test_json_string_characters() {
assert json2.encode([u8(`/`)].bytestr()).bytes() == r'"\/"'.bytes()
assert json2.encode([u8(`\\`)].bytestr()).bytes() == r'"\\"'.bytes()
assert json2.encode([u8(`"`)].bytestr()).bytes() == r'"\""'.bytes()
assert json2.encode([u8(`\n`)].bytestr()).bytes() == r'"\n"'.bytes()
assert json2.encode(r'\n\r') == r'"\\n\\r"'
assert json2.encode('\\n') == r'"\\n"'
assert json2.encode(r'\n\r\b') == r'"\\n\\r\\b"'
assert json2.encode(r'\"/').bytes() == r'"\\\"\/"'.bytes()
assert json2.encode(r'\n\r\b\f\t\\\"\/') == r'"\\n\\r\\b\\f\\t\\\\\\\"\\\/"'
assert json2.encode("fn main(){nprintln('Hello World! Helo \$a')\n}") == '"fn main(){nprintln(\'Hello World! Helo \$a\')\\n}"'
assert json2.encode(' And when "\'s are in the string, along with # "') == '" And when \\"\'s are in the string, along with # \\""'
assert json2.encode('a \\\nb') == r'"a \\\nb"'
assert json2.encode('Name\tJosé\nLocation\tSF.') == '"Name\\tJosé\\nLocation\\tSF."'
}
fn test_json_escape_low_chars() {
esc := '\u001b'
assert esc.len == 1
text := json2.Any(esc)
assert text.json_str() == r'"\u001b"'
assert json2.encode('\u000f') == r'"\u000f"'
assert json2.encode('\u0020') == r'" "'
assert json2.encode('\u0000') == r'"\u0000"'
}
fn test_json_string() {
text := json2.Any('test')
assert text.json_str() == r'"te\u2714st"'
assert json2.encode('test') == r'"te\u2714st"'
boolean := json2.Any(true)
assert boolean.json_str() == 'true'
integer := json2.Any(int(-5))
assert integer.json_str() == '-5'
u64integer := json2.Any(u64(5000))
assert u64integer.json_str() == '5000'
i64integer := json2.Any(i64(-17))
assert i64integer.json_str() == '-17'
}
fn test_json_string_emoji() {
text := json2.Any('🐈')
assert text.json_str() == r'"🐈"'
assert json2.Any('💀').json_str() == r'"💀"'
assert json2.encode('🐈') == r'"🐈"'
assert json2.encode('💀') == r'"💀"'
assert json2.encode('🐈💀') == r'"🐈💀"'
}
fn test_json_string_non_ascii() {
text := json2.Any('')
assert text.json_str() == r'"\u3072\u3089\u304c\u306a"'
assert json2.encode('') == r'"\u3072\u3089\u304c\u306a"'
}
fn test_utf8_strings_are_not_modified() {
original := '{"s":"Schilddrüsenerkrankungen"}'
deresult := json.decode[json2.Any](original)!
assert deresult.str() == original
assert json2.encode('ü') == '"ü"'
assert json2.encode('Schilddrüsenerkrankungen') == '"Schilddrüsenerkrankungen"'
}
fn test_encoder_unescaped_utf32() ! {
jap_text := json2.Any('')
enc := json2.Encoder{
escape_unicode: false
}
mut sb := strings.new_builder(20)
defer {
unsafe { sb.free() }
}
enc.encode_value(jap_text, mut sb)!
assert sb.str() == '"${jap_text}"'
sb.go_back_to(0)
emoji_text := json2.Any('🐈')
enc.encode_value(emoji_text, mut sb)!
assert sb.str() == '"${emoji_text}"'
mut buf := []u8{cap: 14}
enc.encode_value('', mut buf)!
assert buf.len == 14
assert buf.bytestr() == '""'
}
fn test_encoder_prettify() {
obj := {
'hello': json2.Any('world')
'arr': [json2.Any('im a string'), [json2.Any('3rd level')]]
'obj': {
'map': json2.Any('map inside a map')
}
}
enc := json2.Encoder{
newline: `\n`
newline_spaces_count: 2
}
mut sb := strings.new_builder(20)
defer {
unsafe { sb.free() }
}
enc.encode_value(obj, mut sb)!
assert sb.str() == '{
"hello": "world",
"arr": [
"im a string",
[
"3rd level"
]
],
"obj": {
"map": "map inside a map"
}
}'
}
pub struct Test {
val string
}
fn test_encode_struct() {
enc := json2.encode(Test{'hello!'})
assert enc == '{"val":"hello!"}'
}
pub struct Uri {
protocol string
path string
}
pub fn (u Uri) json_str() string {
return '"${u.protocol}://${u.path}"'
}
fn test_encode_encodable() {
assert json2.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 json2.encode([1, 2, 3]) == '[1,2,3]'
assert json2.encode(array_of_struct) == '[{"val":[false,true]},{"val":[true,false]}]'
}
fn test_encode_simple() {
assert json2.encode('hello!') == '"hello!"'
assert json2.encode(1) == '1'
}
fn test_encode_value() {
json_enc := json2.Encoder{
newline: `\n`
newline_spaces_count: 2
escape_unicode: false
}
mut manifest := map[string]json2.Any{}
manifest['server_path'] = json2.Any('new_path')
manifest['last_updated'] = json2.Any('timestamp.format_ss()')
manifest['from_source'] = json2.Any('from_source')
mut sb := strings.new_builder(64)
mut buffer := []u8{}
json_enc.encode_value(manifest, mut buffer)!
assert buffer.len > 0
assert buffer == [u8(123), 10, 32, 32, 34, 115, 101, 114, 118, 101, 114, 95, 112, 97, 116,
104, 34, 58, 32, 34, 110, 101, 119, 95, 112, 97, 116, 104, 34, 44, 10, 32, 32, 34, 108,
97, 115, 116, 95, 117, 112, 100, 97, 116, 101, 100, 34, 58, 32, 34, 116, 105, 109, 101,
115, 116, 97, 109, 112, 46, 102, 111, 114, 109, 97, 116, 95, 115, 115, 40, 41, 34, 44,
10, 32, 32, 34, 102, 114, 111, 109, 95, 115, 111, 117, 114, 99, 101, 34, 58, 32, 34, 102,
114, 111, 109, 95, 115, 111, 117, 114, 99, 101, 34, 10, 125]
sb.write(buffer)!
unsafe { buffer.free() }
assert sb.str() == r'{
"server_path": "new_path",
"last_updated": "timestamp.format_ss()",
"from_source": "from_source"
}'
}
fn test_encode_time() {
assert json2.encode({
'bro': json2.Any(time.Time{})
}) == '{"bro":"0000-00-00T00:00:00.000Z"}'
assert json2.encode({
'bro': time.Time{}
}) == '{"bro":"0000-00-00T00:00:00.000Z"}'
assert json2.encode(time.Time{}) == '"0000-00-00T00:00:00.000Z"'
}

View File

@ -0,0 +1,143 @@
import x.json2.decoder2 as json
import x.json2
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 := json2.fast_raw_decode(s) or {
assert false
json2.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 := []json2.Any{}
for variable in array_of_struct {
array_of_map << json2.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 := []json2.Any{}
for variable in array_of_struct {
array_of_map << json2.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 := []json2.Any{}
for variable in array_of_struct {
array_of_map << json2.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 := []json2.Any{}
for variable in array_of_struct {
array_of_map << json2.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 := []json2.Any{}
for variable in array_of_struct_int {
array_of_map_int << json2.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 json2.map_from(StructType[string]{'3'}).str() == '{"val":"3"}'
assert json2.map_from(StructType[bool]{true}).str() == '{"val":true}'
assert json2.map_from(StructType[i8]{3}).str() == '{"val":3}'
assert json2.map_from(StructType[i16]{3}).str() == '{"val":3}'
assert json2.map_from(StructType[int]{3}).str() == '{"val":3}'
assert json2.map_from(StructType[i64]{3}).str() == '{"val":3}'
assert json2.map_from(StructType[i8]{-3}).str() == '{"val":-3}'
assert json2.map_from(StructType[i16]{i16(-3)}).str() == '{"val":-3}'
assert json2.map_from(StructType[int]{-3}).str() == '{"val":-3}'
assert json2.map_from(StructType[i64]{-3}).str() == '{"val":-3}'
assert json2.map_from(StructType[f32]{3.0}).str() == '{"val":3}'
assert json2.map_from(StructType[f64]{3.0}).str() == '{"val":3}'
assert json2.map_from(StructType[u8]{3}).str() == '{"val":3}'
assert json2.map_from(StructType[u16]{3}).str() == '{"val":3}'
assert json2.map_from(StructType[u32]{3}).str() == '{"val":3}'
assert json2.map_from(StructType[u64]{3}).str() == '{"val":3}'
}
fn test_struct_with_struct_to_map() {
assert json2.map_from(StructType[StructType[string]]{StructType[string]{'3'}}).str() == '{"val":{"val":"3"}}'
assert json2.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}
}
}

View File

@ -0,0 +1,84 @@
import x.json2.decoder2 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 {
assert err.msg() == 'empty string'
return
}
assert false
}
fn test_decode_error_message_should_have_enough_context_just_brace() {
json.decode[DbConfig]('{') or {
assert err.msg() == '
{
^ EOF error: expecting a complete object after `{`'
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() == '\n\n}\n ^ Expecting object key after `,`'
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() == '\n{"host": "localhost", "dbname": "alex" "\n ^ invalid value. Unexpected character after string_ end'
return
}
assert false
}

View File

@ -0,0 +1,441 @@
import x.json2.decoder2 as json
import x.json2
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 := json2.encode[Employee](x)
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 := json2.encode(user)
assert s.contains('"reg_date":"2020-12-22T07:23:00.000Z"')
}
fn (mut u User) foo() string {
return json2.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 := json2.encode[User](usr)
// println(out)
assert out == expected
// Test json2.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': json2.Any(1)
'two': json2.Any(2)
'three': json2.Any(3)
'four': json2.Any(4)
}
// numbers := {
// 'one': 1
// 'two': 2
// 'three': 3
// 'four': 4
// }
assert json2.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 := json2.encode[Message](msg)
assert out == expected
}
struct StByteArray {
ba []u8
}
fn test_byte_array() {
assert json2.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 := json2.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 json2.encode_pretty(foo) == '{
"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 := json2.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 json2.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 := json2.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 := json2.encode(game)
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke"},"other":[{"tag":"Pen"},{"tag":"Cookie"},1,"Stool","${t.format_rfc3339()}"]}'
}
struct Foo3 {
name string
age int @[omitempty]
}
// fn test_omit_empty() {
// foo := Foo3{'Bob', 0}
// assert json2.encode(foo) == '{"name":"Bob"}'
// assert json2.encode_pretty(foo) == '{
// "name": "Bob"
// }'
// }
struct Foo31 {
name string
age ?int
}
fn test_option_instead_of_omit_empty() {
foo := Foo31{
name: 'Bob'
}
assert json2.encode_pretty(foo) == '{
"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 json2.encode(data)
}
// fn test_encode_sumtype_defined_ahead() {
// ret := create_game_packet(&GamePacketData(GPScale{}))
// assert ret == '{"value":0}'
// }

View File

@ -17,7 +17,7 @@ pub:
}
// byte array versions of the most common tokens/chars to avoid reallocations
const null_in_bytes = 'null'
const null_in_string = 'null'
const true_in_string = 'true'
@ -180,7 +180,7 @@ fn (e &Encoder) encode_value_with_level[T](val T, level int, mut buf []u8) ! {
str_value := val.json_str()
unsafe { buf.push_many(str_value.str, str_value.len) }
} $else $if T is Null {
unsafe { buf.push_many(null_in_bytes.str, null_in_bytes.len) }
unsafe { buf.push_many(null_in_string.str, null_in_string.len) }
} $else $if T is $struct {
e.encode_struct(val, level, mut buf)!
} $else $if T is $enum {