Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,88 @@
|
||||
## Description
|
||||
|
||||
The `json` module provides encoding/decoding of V data structures to/from JSON.
|
||||
For more details, see also the
|
||||
[JSON section of the V documentation](https://github.com/vlang/v/blob/master/doc/docs.md#json)
|
||||
|
||||
Large `i64` and `u64` values are encoded as exact decimal JSON numbers, and
|
||||
`json.decode` preserves those integer values when reading decimal integer input.
|
||||
|
||||
Struct fields of type `time.Time` can be decoded from either a JSON number or
|
||||
from a JSON string in ISO 8601, RFC 3339, or Unix-timestamp form.
|
||||
|
||||
## Examples
|
||||
|
||||
Here is an example of encoding and decoding a V struct with several fields.
|
||||
Note that you can specify different names in the json encoding for the fields,
|
||||
and that you can skip fields too, if needed.
|
||||
|
||||
```v
|
||||
import json
|
||||
|
||||
enum JobTitle {
|
||||
manager
|
||||
executive
|
||||
worker
|
||||
}
|
||||
|
||||
struct Employee {
|
||||
mut:
|
||||
name string
|
||||
family string @[json: '-'] // this field will be skipped
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle @[json: 'ETitle'] // the key for this field will be 'ETitle', not 'title'
|
||||
// the JSON property is omitted while the field keeps its zero/default value
|
||||
notes string @[omitempty]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := Employee{'Peter', 'Begins', 28, 95000.5, .worker, ''}
|
||||
println(x)
|
||||
s := json.encode(x)
|
||||
println('JSON encoding of employee x: ${s}')
|
||||
assert s == '{"name":"Peter","age":28,"salary":95000.5,"ETitle":"worker"}'
|
||||
mut y := json.decode(Employee, s)!
|
||||
assert y != x
|
||||
assert y.family == ''
|
||||
y.family = 'Begins'
|
||||
assert y == x
|
||||
println(y)
|
||||
ss := json.encode(y)
|
||||
println('JSON encoding of employee y: ${ss}')
|
||||
assert ss == s
|
||||
}
|
||||
```
|
||||
|
||||
### JSON field attributes
|
||||
|
||||
The `json` module supports a few field attributes for controlling how struct fields map to JSON:
|
||||
|
||||
- `@[json: 'name']` uses a different JSON key for the field.
|
||||
- `@[json: '-']` skips the field.
|
||||
- `@[omitempty]` omits the field while encoding if it still has its zero/default value.
|
||||
- `@[raw]` decodes the field as raw JSON text instead of decoding it into another V value.
|
||||
This is useful when you want to keep a nested JSON fragment in a `string` or `?string`
|
||||
field.
|
||||
|
||||
```v
|
||||
import json
|
||||
|
||||
struct Message {
|
||||
payload string @[raw]
|
||||
note ?string @[omitempty]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
msg := json.decode(Message, '{"payload":{"kind":"ping"},"note":""}')!
|
||||
assert msg.payload == '{"kind":"ping"}'
|
||||
|
||||
out := json.encode(Message{
|
||||
payload: '{"kind":"ping"}'
|
||||
})
|
||||
assert out == '{"payload":"{\\"kind\\":\\"ping\\"}"}'
|
||||
}
|
||||
```
|
||||
|
||||
In the example above, `payload` keeps the original JSON fragment during decoding,
|
||||
while `note` is omitted during encoding when it is `none`.
|
||||
@@ -0,0 +1,23 @@
|
||||
import json.cjson
|
||||
|
||||
fn test_object_with_null() {
|
||||
mut root := cjson.create_object()
|
||||
root.add_item_to_object('name', cjson.create_string('Andre'))
|
||||
root.add_item_to_object('age', cjson.create_null())
|
||||
assert root.print_unformatted() == '{"name":"Andre","age":null}'
|
||||
unsafe { cjson.delete(root) }
|
||||
}
|
||||
|
||||
fn test_creating_complex_json() {
|
||||
mut root := cjson.create_array()
|
||||
root.add_item_to_array(cjson.create_string('user'))
|
||||
mut obj := cjson.create_object()
|
||||
obj.add_item_to_object('username', cjson.create_string('foo'))
|
||||
obj.add_item_to_object('password', cjson.create_string('bar'))
|
||||
root.add_item_to_array(obj)
|
||||
result := root.print_unformatted()
|
||||
println(result)
|
||||
|
||||
assert result == '["user",{"username":"foo","password":"bar"}]'
|
||||
unsafe { cjson.delete(root) }
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module cjson
|
||||
|
||||
// The `json.cjson` module provides more manual and low level access to the cJSON library.
|
||||
// For example, using it, it is possible to build a tree in memory, that represents a JSON object,
|
||||
// that has NULL leaves for example, which is currently not convenient/easy to do with just the
|
||||
// high level `json.encode(value)` API that V has.
|
||||
|
||||
#flag -I @VEXEROOT/thirdparty/cJSON
|
||||
#flag @VEXEROOT/thirdparty/cJSON/cJSON.o
|
||||
#include "cJSON.h"
|
||||
|
||||
// As cJSON use `libm`, we need to link it.
|
||||
$if windows {
|
||||
$if tinyc {
|
||||
#flag @VEXEROOT/thirdparty/tcc/lib/openlibm.o
|
||||
}
|
||||
} $else {
|
||||
#flag -lm
|
||||
}
|
||||
|
||||
@[flag]
|
||||
pub enum CJsonType {
|
||||
t_false
|
||||
t_true
|
||||
t_null
|
||||
t_number
|
||||
t_string
|
||||
t_array
|
||||
t_object
|
||||
t_raw
|
||||
}
|
||||
|
||||
@[typedef]
|
||||
pub struct C.cJSON {
|
||||
pub:
|
||||
next &C.cJSON // next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem
|
||||
prev &C.cJSON
|
||||
child &C.cJSON // An array or object item will have a child pointer pointing to a chain of the items in the array/object
|
||||
|
||||
type CJsonType // The type of the item, as above
|
||||
|
||||
valueint int // writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead
|
||||
valuedouble f64 // The item's number, if type==cJSON_Number
|
||||
valuestring &char // The item's string, if type==cJSON_String and type == cJSON_Raw
|
||||
// @string &char // The item's name string, if this item is the child of, or is in the list of subitems of an object
|
||||
// TODO: `@string &char` from above does not work. It should be fixed, at least inside `struct C.`.
|
||||
}
|
||||
|
||||
pub type Node = C.cJSON
|
||||
|
||||
fn C.cJSON_Version() &char
|
||||
|
||||
fn C.cJSON_Parse(const_value &char) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateObject() &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateArray() &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateBool(bool) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateTrue() &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateFalse() &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateNull() &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateNumber(f64) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateString(const_s &char) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateRaw(const_s &char) &C.cJSON
|
||||
|
||||
fn C.cJSON_IsInvalid(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsFalse(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsTrue(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsBool(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsNull(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsNumber(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsString(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsArray(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsObject(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_IsRaw(object &C.cJSON) bool
|
||||
|
||||
fn C.cJSON_AddItemToObject(object &C.cJSON, const_key &char, item &C.cJSON)
|
||||
|
||||
fn C.cJSON_AddItemToArray(object &C.cJSON, item &C.cJSON)
|
||||
|
||||
fn C.cJSON_Delete(object &C.cJSON)
|
||||
|
||||
fn C.cJSON_Print(object &C.cJSON) &char
|
||||
|
||||
fn C.cJSON_PrintUnformatted(object &C.cJSON) &char
|
||||
|
||||
fn C.cJSON_free(voidptr)
|
||||
|
||||
//
|
||||
|
||||
// version returns the version of cJSON as a string.
|
||||
pub fn version() string {
|
||||
return unsafe { tos3(&char(C.cJSON_Version())) }
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// create_object creates a new JSON object/map item. Use .add_item_to_object(key, value) calls, to add other items to it later.
|
||||
pub fn create_object() &Node {
|
||||
return C.cJSON_CreateObject()
|
||||
}
|
||||
|
||||
// create_array creates a new JSON array item. Use .add_item_to_array(value) calls, to add items to it later.
|
||||
pub fn create_array() &Node {
|
||||
return C.cJSON_CreateArray()
|
||||
}
|
||||
|
||||
// create_string creates a new JSON string item.
|
||||
pub fn create_string(val string) &Node {
|
||||
return C.cJSON_CreateString(&char(val.str))
|
||||
}
|
||||
|
||||
// create_raw creates a new JSON RAW string item.
|
||||
pub fn create_raw(const_val string) &Node {
|
||||
return C.cJSON_CreateRaw(&char(const_val.str))
|
||||
}
|
||||
|
||||
// create_number creates a new JSON number item.
|
||||
pub fn create_number(val f64) &Node {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
// create_bool creates a new JSON boolean item.
|
||||
pub fn create_bool(val bool) &Node {
|
||||
return C.cJSON_CreateBool(val)
|
||||
}
|
||||
|
||||
// create_true creates a new JSON boolean item, with value `true`.
|
||||
pub fn create_true() &Node {
|
||||
return C.cJSON_CreateTrue()
|
||||
}
|
||||
|
||||
// create_false creates a new JSON boolean item, with value `false`.
|
||||
pub fn create_false() &Node {
|
||||
return C.cJSON_CreateFalse()
|
||||
}
|
||||
|
||||
// create_null creates a new JSON NULL item, with the value `null`. It symbolises a missing value for a given key in an object.
|
||||
pub fn create_null() &Node {
|
||||
return C.cJSON_CreateNull()
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// delete removes the given node from memory.
|
||||
// NB: DO NOT USE that node, after you have called `unsafe { delete(node) }` !
|
||||
@[unsafe]
|
||||
pub fn delete(node &Node) {
|
||||
C.cJSON_Delete(node)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// add_item_to_array adds the given item to the object, under the given `key`.
|
||||
pub fn (mut obj Node) add_item_to_object(key string, item &Node) {
|
||||
C.cJSON_AddItemToObject(obj, &char(key.str), item)
|
||||
}
|
||||
|
||||
// add_item_to_array append the given item to the object.
|
||||
pub fn (mut obj Node) add_item_to_array(item &Node) {
|
||||
C.cJSON_AddItemToArray(obj, item)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// print serialises the node to a string, formatting its structure, so the resulting string is more prettier/human readable.
|
||||
pub fn (mut obj Node) print() string {
|
||||
mut s := C.cJSON_Print(obj)
|
||||
return unsafe { tos3(s) }
|
||||
}
|
||||
|
||||
// print serialises the node to a string, without formatting its structure, so the resulting string is shorter/cheaper to transmit.
|
||||
pub fn (mut obj Node) print_unformatted() string {
|
||||
mut s := C.cJSON_PrintUnformatted(obj)
|
||||
ret := unsafe { tos_clone(&u8(s)) }
|
||||
C.cJSON_free(s)
|
||||
return ret
|
||||
}
|
||||
|
||||
// str returns the unformatted serialisation to string of the given Node.
|
||||
pub fn (mut obj Node) str() string {
|
||||
return obj.print_unformatted()
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module json
|
||||
|
||||
import math
|
||||
import strconv
|
||||
import time
|
||||
|
||||
#flag -I @VEXEROOT/thirdparty/cJSON
|
||||
#flag @VEXEROOT/thirdparty/cJSON/cJSON.o
|
||||
#include "cJSON.h"
|
||||
#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key))
|
||||
|
||||
// As cJSON use `libm`, we need to link it.
|
||||
$if windows {
|
||||
$if tinyc {
|
||||
#flag @VEXEROOT/thirdparty/tcc/lib/openlibm.o
|
||||
}
|
||||
} $else {
|
||||
#flag -lm
|
||||
}
|
||||
|
||||
pub struct C.cJSON {
|
||||
next &C.cJSON
|
||||
prev &C.cJSON
|
||||
child &C.cJSON
|
||||
type int
|
||||
valuestring &char
|
||||
valueint int
|
||||
valuedouble f64
|
||||
}
|
||||
|
||||
fn C.cJSON_IsTrue(&C.cJSON) bool
|
||||
fn C.cJSON_IsFalse(&C.cJSON) bool
|
||||
fn C.cJSON_IsNull(&C.cJSON) bool
|
||||
fn C.cJSON_IsNumber(&C.cJSON) bool
|
||||
fn C.cJSON_IsString(&C.cJSON) bool
|
||||
fn C.cJSON_IsObject(&C.cJSON) bool
|
||||
fn C.cJSON_IsArray(&C.cJSON) bool
|
||||
|
||||
fn C.cJSON_CreateNumber(f64) &C.cJSON
|
||||
fn C.cJSON_CreateRaw(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateBool(bool) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateString(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_CreateRaw(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_Parse(&char) &C.cJSON
|
||||
|
||||
fn C.cJSON_PrintUnformatted(&C.cJSON) &char
|
||||
|
||||
fn C.cJSON_Print(&C.cJSON) &char
|
||||
|
||||
fn C.cJSON_free(voidptr)
|
||||
fn C.malloc(usize) voidptr
|
||||
fn C.memcpy(voidptr, voidptr, usize) voidptr
|
||||
|
||||
// decode tries to decode the provided JSON string, into a V structure.
|
||||
// If it can not do that, it returns an error describing the reason for
|
||||
// the parsing failure.
|
||||
pub fn decode(typ voidptr, s string) !voidptr {
|
||||
// compiler implementation
|
||||
return 0
|
||||
}
|
||||
|
||||
// encode serialises the provided V value as a JSON string, optimised for shortness.
|
||||
pub fn encode(x voidptr) string {
|
||||
// compiler implementation
|
||||
return ''
|
||||
}
|
||||
|
||||
// encode_pretty serialises the provided V value as a JSON string, in a formatted way, optimised for viewing by humans.
|
||||
pub fn encode_pretty(x voidptr) string {
|
||||
// compiler implementation
|
||||
return ''
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_int(root &C.cJSON) int {
|
||||
if isnil(root) {
|
||||
return 0
|
||||
}
|
||||
return root.valueint
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_i8(root &C.cJSON) i8 {
|
||||
if isnil(root) {
|
||||
return i8(0)
|
||||
}
|
||||
return i8(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_i16(root &C.cJSON) i16 {
|
||||
if isnil(root) {
|
||||
return i16(0)
|
||||
}
|
||||
return i16(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_i64(root &C.cJSON) i64 {
|
||||
if isnil(root) {
|
||||
return i64(0)
|
||||
}
|
||||
if value := decode_exact_i64(root) {
|
||||
return value
|
||||
}
|
||||
return i64(root.valuedouble) // i64 is double in C
|
||||
}
|
||||
|
||||
// TODO: remove when `byte` is removed
|
||||
@[markused]
|
||||
fn decode_byte(root &C.cJSON) u8 {
|
||||
return decode_u8(root)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u8(root &C.cJSON) u8 {
|
||||
if isnil(root) {
|
||||
return u8(0)
|
||||
}
|
||||
return u8(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u16(root &C.cJSON) u16 {
|
||||
if isnil(root) {
|
||||
return u16(0)
|
||||
}
|
||||
return u16(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u32(root &C.cJSON) u32 {
|
||||
if isnil(root) {
|
||||
return u32(0)
|
||||
}
|
||||
return u32(root.valueint)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_u64(root &C.cJSON) u64 {
|
||||
if isnil(root) {
|
||||
return u64(0)
|
||||
}
|
||||
if value := decode_exact_u64(root) {
|
||||
return value
|
||||
}
|
||||
return u64(root.valuedouble)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_f32(root &C.cJSON) f32 {
|
||||
if isnil(root) {
|
||||
return f32(0)
|
||||
}
|
||||
return f32(root.valuedouble)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_f64(root &C.cJSON) f64 {
|
||||
if isnil(root) {
|
||||
return f64(0)
|
||||
}
|
||||
return root.valuedouble
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_rune(root &C.cJSON) rune {
|
||||
if isnil(root) {
|
||||
return rune(0)
|
||||
}
|
||||
if isnil(root.valuestring) || !C.cJSON_IsString(root) {
|
||||
return rune(0)
|
||||
}
|
||||
|
||||
// TODO: Parse as runes, bypassing string casting...?
|
||||
return unsafe { tos_clone(&u8(root.valuestring)).runes().first() }
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_string(root &C.cJSON) string {
|
||||
if isnil(root) {
|
||||
return ''
|
||||
}
|
||||
if !isnil(root.valuestring) && C.cJSON_IsString(root) {
|
||||
return unsafe { tos_clone(&u8(root.valuestring)) } // , _strlen(root.valuestring))
|
||||
}
|
||||
// Object/array values can be stringified JSON payloads (e.g. `{}` from JSON.stringify()).
|
||||
if C.cJSON_IsObject(root) || C.cJSON_IsArray(root) {
|
||||
return json_print(root)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_bool(root &C.cJSON) bool {
|
||||
if isnil(root) {
|
||||
return false
|
||||
}
|
||||
return C.cJSON_IsTrue(root)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn decode_time(root &C.cJSON) !time.Time {
|
||||
if isnil(root) || C.cJSON_IsNull(root) {
|
||||
return time.Time{}
|
||||
}
|
||||
mut decoded_time := time.Time{}
|
||||
if C.cJSON_IsString(root) {
|
||||
decoded_time.from_json_string(decode_string(root))!
|
||||
return decoded_time
|
||||
}
|
||||
if C.cJSON_IsNumber(root) {
|
||||
decoded_time.from_json_number(json_print(root))!
|
||||
return decoded_time
|
||||
}
|
||||
return error('expected time.Time to decode from a JSON string or number, got: ${json_print(root)}')
|
||||
}
|
||||
|
||||
// ///////////////////
|
||||
|
||||
@[markused]
|
||||
fn encode_int(val int) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_i8(val i8) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_i16(val i16) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_i64(val i64) &C.cJSON {
|
||||
lit := val.str()
|
||||
return C.cJSON_CreateRaw(&char(lit.str))
|
||||
}
|
||||
|
||||
// TODO: remove when `byte` is removed
|
||||
@[markused]
|
||||
fn encode_byte(root u8) &C.cJSON {
|
||||
return encode_u8(root)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u8(val u8) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u16(val u16) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u32(val u32) &C.cJSON {
|
||||
return C.cJSON_CreateNumber(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_u64(val u64) &C.cJSON {
|
||||
lit := val.str()
|
||||
return C.cJSON_CreateRaw(&char(lit.str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_f32(val f32) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_float_to_raw_string(val).str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_f64(val f64) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_float_to_raw_string(val).str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_bool(val bool) &C.cJSON {
|
||||
return C.cJSON_CreateBool(val)
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_rune(val rune) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_ascii_string(val.str()).str))
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn encode_string(val string) &C.cJSON {
|
||||
return C.cJSON_CreateRaw(&char(json_ascii_string(val).str))
|
||||
}
|
||||
|
||||
// json_ascii_string returns a quoted JSON string with non-ASCII runes escaped as `\uXXXX`.
|
||||
fn json_ascii_string(val string) string {
|
||||
mut output := []u8{cap: val.len + 2}
|
||||
output << `"`
|
||||
for character in val.runes() {
|
||||
match character {
|
||||
`"` {
|
||||
output << `\\`
|
||||
output << `"`
|
||||
}
|
||||
`\\` {
|
||||
output << `\\`
|
||||
output << `\\`
|
||||
}
|
||||
`\b` {
|
||||
output << `\\`
|
||||
output << `b`
|
||||
}
|
||||
`\f` {
|
||||
output << `\\`
|
||||
output << `f`
|
||||
}
|
||||
`\n` {
|
||||
output << `\\`
|
||||
output << `n`
|
||||
}
|
||||
`\r` {
|
||||
output << `\\`
|
||||
output << `r`
|
||||
}
|
||||
`\t` {
|
||||
output << `\\`
|
||||
output << `t`
|
||||
}
|
||||
else {
|
||||
if character < 0x20 || character > 0x7f {
|
||||
if character <= 0xffff {
|
||||
output << `\\`
|
||||
output << `u`
|
||||
hex_string := '${u32(character):04x}'
|
||||
unsafe { output.push_many(hex_string.str, 4) }
|
||||
} else {
|
||||
unicode_point_low := u32(character) - 0x10000
|
||||
surrogate_pair := '\\u${0xD800 + ((unicode_point_low >> 10) & 0x3FF):04X}\\u${
|
||||
0xDC00 + (unicode_point_low & 0x3FF):04x}'
|
||||
unsafe { output.push_many(surrogate_pair.str, surrogate_pair.len) }
|
||||
}
|
||||
} else {
|
||||
output << u8(character)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output << `"`
|
||||
return output.bytestr()
|
||||
}
|
||||
|
||||
// json_float_to_raw_string uses V's float formatter so json.encode keeps exact float round-trips.
|
||||
fn json_float_to_raw_string[T](val T) string {
|
||||
if val == 0 {
|
||||
return '0'
|
||||
}
|
||||
if math.is_nan(f64(val)) || math.is_inf(f64(val), 0) {
|
||||
return 'null'
|
||||
}
|
||||
mut raw := val.str()
|
||||
if raw.len > 2 && raw[raw.len - 2] == `.` && raw[raw.len - 1] == `0` {
|
||||
raw = raw[..raw.len - 2]
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
fn decode_exact_i64(root &C.cJSON) ?i64 {
|
||||
if isnil(root) || isnil(root.valuestring) {
|
||||
return none
|
||||
}
|
||||
lit := unsafe { tos_clone(&u8(root.valuestring)) }
|
||||
value := strconv.parse_int(lit, 10, 64) or { return none }
|
||||
return value
|
||||
}
|
||||
|
||||
fn decode_exact_u64(root &C.cJSON) ?u64 {
|
||||
if isnil(root) || isnil(root.valuestring) {
|
||||
return none
|
||||
}
|
||||
lit := unsafe { tos_clone(&u8(root.valuestring)) }
|
||||
value := strconv.parse_uint(lit, 10, 64) or { return none }
|
||||
return value
|
||||
}
|
||||
|
||||
fn clone_cstring(s string) &char {
|
||||
buf := C.malloc(usize(s.len + 1))
|
||||
if buf == unsafe { nil } {
|
||||
return unsafe { nil }
|
||||
}
|
||||
unsafe {
|
||||
if s.len > 0 {
|
||||
C.memcpy(buf, s.str, usize(s.len))
|
||||
}
|
||||
(&u8(buf))[s.len] = 0
|
||||
}
|
||||
return &char(buf)
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn is_json_whitespace(ch u8) bool {
|
||||
return ch == ` ` || ch == `\n` || ch == `\r` || ch == `\t`
|
||||
}
|
||||
|
||||
fn skip_json_whitespace(src string, idx int) int {
|
||||
mut pos := idx
|
||||
for pos < src.len && is_json_whitespace(src[pos]) {
|
||||
pos++
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
fn skip_json_string(src string, idx int) int {
|
||||
mut pos := idx
|
||||
if pos >= src.len || src[pos] != `"` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
for pos < src.len {
|
||||
match src[pos] {
|
||||
`"` {
|
||||
return pos + 1
|
||||
}
|
||||
`\\` {
|
||||
pos++
|
||||
if pos >= src.len {
|
||||
return -1
|
||||
}
|
||||
if src[pos] == `u` {
|
||||
pos += 5
|
||||
} else {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
else {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fn skip_json_literal(src string, lit string, idx int) int {
|
||||
if idx + lit.len > src.len || src[idx..idx + lit.len] != lit {
|
||||
return -1
|
||||
}
|
||||
return idx + lit.len
|
||||
}
|
||||
|
||||
fn skip_json_number(src string, idx int) (string, int) {
|
||||
mut pos := idx
|
||||
start := pos
|
||||
if pos < src.len && src[pos] == `-` {
|
||||
pos++
|
||||
}
|
||||
if pos >= src.len {
|
||||
return '', -1
|
||||
}
|
||||
if src[pos] == `0` {
|
||||
pos++
|
||||
} else {
|
||||
if !src[pos].is_digit() {
|
||||
return '', -1
|
||||
}
|
||||
for pos < src.len && src[pos].is_digit() {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
if pos < src.len && src[pos] == `.` {
|
||||
pos++
|
||||
if pos >= src.len || !src[pos].is_digit() {
|
||||
return '', -1
|
||||
}
|
||||
for pos < src.len && src[pos].is_digit() {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
if pos < src.len && (src[pos] == `e` || src[pos] == `E`) {
|
||||
pos++
|
||||
if pos < src.len && (src[pos] == `+` || src[pos] == `-`) {
|
||||
pos++
|
||||
}
|
||||
if pos >= src.len || !src[pos].is_digit() {
|
||||
return '', -1
|
||||
}
|
||||
for pos < src.len && src[pos].is_digit() {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
return src[start..pos], pos
|
||||
}
|
||||
|
||||
fn annotate_json_number_literal(root &C.cJSON, lit string) {
|
||||
if lit.len == 0 || !isnil(root.valuestring) {
|
||||
return
|
||||
}
|
||||
unsafe {
|
||||
root.valuestring = clone_cstring(lit)
|
||||
}
|
||||
}
|
||||
|
||||
fn annotate_json_value(root &C.cJSON, src string, idx int) int {
|
||||
if isnil(root) {
|
||||
return -1
|
||||
}
|
||||
mut pos := skip_json_whitespace(src, idx)
|
||||
if C.cJSON_IsObject(root) {
|
||||
if pos >= src.len || src[pos] != `{` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
mut child := root.child
|
||||
if isnil(child) {
|
||||
if pos >= src.len || src[pos] != `}` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
for !isnil(child) {
|
||||
pos = skip_json_string(src, pos)
|
||||
if pos == -1 {
|
||||
return -1
|
||||
}
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
if pos >= src.len || src[pos] != `:` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
pos = annotate_json_value(child, src, pos)
|
||||
if pos == -1 {
|
||||
return -1
|
||||
}
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
if !isnil(child.next) {
|
||||
if pos >= src.len || src[pos] != `,` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
}
|
||||
child = child.next
|
||||
}
|
||||
if pos >= src.len || src[pos] != `}` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
if C.cJSON_IsArray(root) {
|
||||
if pos >= src.len || src[pos] != `[` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
mut child := root.child
|
||||
if isnil(child) {
|
||||
if pos >= src.len || src[pos] != `]` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
for !isnil(child) {
|
||||
pos = annotate_json_value(child, src, pos)
|
||||
if pos == -1 {
|
||||
return -1
|
||||
}
|
||||
pos = skip_json_whitespace(src, pos)
|
||||
if !isnil(child.next) {
|
||||
if pos >= src.len || src[pos] != `,` {
|
||||
return -1
|
||||
}
|
||||
pos++
|
||||
}
|
||||
child = child.next
|
||||
}
|
||||
if pos >= src.len || src[pos] != `]` {
|
||||
return -1
|
||||
}
|
||||
return pos + 1
|
||||
}
|
||||
if C.cJSON_IsString(root) {
|
||||
return skip_json_string(src, pos)
|
||||
}
|
||||
if C.cJSON_IsNumber(root) {
|
||||
lit, next_pos := skip_json_number(src, pos)
|
||||
if next_pos == -1 {
|
||||
return -1
|
||||
}
|
||||
annotate_json_number_literal(root, lit)
|
||||
return next_pos
|
||||
}
|
||||
if C.cJSON_IsTrue(root) {
|
||||
return skip_json_literal(src, 'true', pos)
|
||||
}
|
||||
if C.cJSON_IsFalse(root) {
|
||||
return skip_json_literal(src, 'false', pos)
|
||||
}
|
||||
if C.cJSON_IsNull(root) {
|
||||
return skip_json_literal(src, 'null', pos)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fn annotate_json_number_literals(root &C.cJSON, src string) {
|
||||
_ := annotate_json_value(root, src, 0)
|
||||
}
|
||||
|
||||
// ///////////////////////
|
||||
// user := decode_User(json_parse(js_string_var))
|
||||
@[markused]
|
||||
fn json_parse(s string) &C.cJSON {
|
||||
root := C.cJSON_Parse(&char(s.str))
|
||||
if !isnil(root) {
|
||||
annotate_json_number_literals(root, s)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
// json_string := json_print(encode_User(user))
|
||||
@[markused]
|
||||
fn json_print(data &C.cJSON) string {
|
||||
s := C.cJSON_PrintUnformatted(data)
|
||||
if s == unsafe { nil } {
|
||||
return ''
|
||||
}
|
||||
r := unsafe { tos_clone(&u8(s)) }
|
||||
C.cJSON_free(s)
|
||||
return r
|
||||
}
|
||||
|
||||
@[markused]
|
||||
fn json_print_pretty(data &C.cJSON) string {
|
||||
s := C.cJSON_Print(data)
|
||||
if s == unsafe { nil } {
|
||||
return ''
|
||||
}
|
||||
r := unsafe { tos_clone(&u8(s)) }
|
||||
C.cJSON_free(s)
|
||||
return r
|
||||
}
|
||||
|
||||
// / cjson wrappers
|
||||
// fn json_array_for_each(val, root &C.cJSON) {
|
||||
// #cJSON_ArrayForEach (val ,root)
|
||||
// }
|
||||
@@ -0,0 +1,26 @@
|
||||
import json
|
||||
|
||||
pub struct User {
|
||||
name string
|
||||
age int
|
||||
height f64
|
||||
}
|
||||
|
||||
type Users = map[string]User
|
||||
|
||||
const json_users = '{
|
||||
"tom": { "name": "Tom", "age": 45, "height": 1.97 },
|
||||
"martin": { "name": "Martin", "age": 40, "height": 1.8 }
|
||||
}'
|
||||
|
||||
fn test_alias_with_map() {
|
||||
a := json.decode(map[string]User, json_users)!
|
||||
b := json.decode(Users, json_users)!
|
||||
|
||||
assert Users(a) == b
|
||||
|
||||
c := json.encode(a)
|
||||
d := json.encode(b)
|
||||
|
||||
assert c == d
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import json
|
||||
|
||||
fn test_main() {
|
||||
json_text := '{ "a": "b" }'
|
||||
b := json.decode(struct {
|
||||
a string
|
||||
}, json_text)!.a
|
||||
assert dump(b) == 'b'
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import json
|
||||
|
||||
struct Test {
|
||||
a string
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
x := json.decode([]&Test, '[{"a":"a"}]') or { exit(1) }
|
||||
assert x[0].a == 'a'
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
module main
|
||||
|
||||
import json
|
||||
|
||||
struct Json2 {
|
||||
inner []f64
|
||||
}
|
||||
|
||||
struct Json {
|
||||
Json2
|
||||
test f64
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
str := '{
|
||||
"inner": [1, 2, 3, 4, 5],
|
||||
"test": 1.0
|
||||
}'
|
||||
data := json.decode(Json, str) or {
|
||||
eprintln('Failed to decode json, error: ${err}')
|
||||
return
|
||||
}
|
||||
println(data)
|
||||
assert data.inner.len == 5
|
||||
assert data.inner[0] == 1.0
|
||||
assert data.inner[4] == 5.0
|
||||
assert data.test == 1.0
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
module main
|
||||
|
||||
import json
|
||||
|
||||
struct Req {
|
||||
height ?int
|
||||
width ?i32
|
||||
}
|
||||
|
||||
const payload = '{}'
|
||||
|
||||
fn test_main() {
|
||||
r := json.decode(Req, payload) or { panic(err) }
|
||||
assert r.height == none
|
||||
assert r.width == none
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import json
|
||||
|
||||
struct Empty {}
|
||||
|
||||
struct SomeStruct {
|
||||
random_field_a ?string
|
||||
random_field_b ?string
|
||||
empty_field ?Empty
|
||||
}
|
||||
|
||||
type Alias = SomeStruct
|
||||
|
||||
fn test_main() {
|
||||
data := json.decode(Alias, '{"empty_field":{}}')!
|
||||
assert data.str() == 'Alias(SomeStruct{
|
||||
random_field_a: Option(none)
|
||||
random_field_b: Option(none)
|
||||
empty_field: Option(Empty{})
|
||||
})'
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
|
||||
enum Lang {
|
||||
en = 1
|
||||
}
|
||||
|
||||
struct Request {
|
||||
lang ?Lang // ?string, ?int are ok
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
assert dump(json.decode(Request, '{}')!) == Request{
|
||||
lang: ?Lang(none)
|
||||
}
|
||||
assert dump(json.decode(Request, '{"lang": "en"}')!) == Request{
|
||||
lang: .en
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import json
|
||||
|
||||
struct Bar {
|
||||
b []int = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}
|
||||
|
||||
struct Foo {
|
||||
Bar
|
||||
a []int = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
str := json.encode(Foo{})
|
||||
assert json.decode(Foo, str)!.str() == 'Foo{
|
||||
Bar: Bar{
|
||||
b: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}
|
||||
a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
}'
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import json
|
||||
|
||||
struct Message {
|
||||
mut:
|
||||
id int
|
||||
text string
|
||||
reply_to &Message
|
||||
}
|
||||
|
||||
struct OptionalMessage {
|
||||
mut:
|
||||
id int
|
||||
text string
|
||||
reply_to ?&OptionalMessage
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
mut json_data := '{"id": 1, "text": "Hello", "reply_to": {"id": 2, "text": "Hi"}}'
|
||||
mut message := json.decode(Message, json_data)!
|
||||
assert message.reply_to.id == 2
|
||||
|
||||
json_data = '{"id": 1, "text": "Hello", "reply_to": {"id": 2, "text": "Hi", "reply_to": {}}}'
|
||||
message = json.decode(Message, json_data)!
|
||||
assert message.reply_to.reply_to.reply_to == unsafe { nil }
|
||||
|
||||
json_data = '{"id": 1, "text": "Hello", "reply_to": {"id": 2, "text": "Hi", "reply_to": {"id": 5}}}'
|
||||
message = json.decode(Message, json_data)!
|
||||
assert message.reply_to.reply_to.id == 5
|
||||
}
|
||||
|
||||
fn test_recursive_optional_struct_ptr() {
|
||||
json_data := '{"id": 1, "text": "Hello", "reply_to": {"id": 2, "text": "Hi", "reply_to": null}}'
|
||||
message := json.decode(OptionalMessage, json_data)!
|
||||
reply := message.reply_to or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
|
||||
assert reply.id == 2
|
||||
assert reply.text == 'Hi'
|
||||
assert reply.reply_to == none
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import json
|
||||
|
||||
pub struct Response {
|
||||
pub:
|
||||
results []Result = []Result{len: 0} @[json: list]
|
||||
tags []string = []string{len: 0} @[json: tags]
|
||||
kind string @[json: result_type]
|
||||
}
|
||||
|
||||
pub struct Result {
|
||||
pub:
|
||||
id int @[json: defid]
|
||||
author string @[json: author]
|
||||
definition string @[json: definition]
|
||||
link string @[json: permalink]
|
||||
thumbs_down int @[json: thumbs_down]
|
||||
thumbs_up int @[json: thumbs_up]
|
||||
word string @[json: word]
|
||||
date string @[json: written_on]
|
||||
audio_samples []string @[json: sound_urls]
|
||||
example string @[json: example]
|
||||
}
|
||||
|
||||
pub fn define(word string) !&Response {
|
||||
resp := '{"list":[{"defid":3439287}]}'
|
||||
response := json.decode(Response, resp) or { return err }
|
||||
return &response
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
response := define('')!
|
||||
assert response.results.len > 0
|
||||
assert response.results[0].id == 3439287
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import 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(?int(none))]
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import 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)
|
||||
}
|
||||
|
||||
fn test_decode_large_u64_from_decimal_json() {
|
||||
cases := [
|
||||
u64(9007199254740991),
|
||||
u64(9007199254740992),
|
||||
u64(9007199254740993),
|
||||
u64(9223372036854775807),
|
||||
u64(9223372036854775808),
|
||||
u64(9223372036854775809),
|
||||
u64(18446744073709551614),
|
||||
u64(18446744073709551615),
|
||||
]
|
||||
for want in cases {
|
||||
got := json.decode([]u64, '[${want}]')!
|
||||
assert got.len == 1
|
||||
assert got[0] == want
|
||||
}
|
||||
}
|
||||
|
||||
fn test_encode_decode_large_u64_roundtrip() {
|
||||
cases := [
|
||||
u64(9007199254740991),
|
||||
u64(9007199254740992),
|
||||
u64(9007199254740993),
|
||||
u64(9223372036854775807),
|
||||
u64(9223372036854775808),
|
||||
u64(9223372036854775809),
|
||||
u64(18446744073709551614),
|
||||
u64(18446744073709551615),
|
||||
]
|
||||
for want in cases {
|
||||
encoded := json.encode([want])
|
||||
assert encoded == '[${want}]'
|
||||
got := json.decode([]u64, encoded)!
|
||||
assert got.len == 1
|
||||
assert got[0] == want
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
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]
|
||||
skip_field string @[json: '-']
|
||||
}
|
||||
|
||||
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 == []
|
||||
}
|
||||
|
||||
fn test_skip_should_be_ignored() {
|
||||
data := '{"total_comments": 55, "id": 123, "skip_field": "foo"}'
|
||||
mut task := json.decode(Task, data)!
|
||||
assert task.id == 123
|
||||
assert task.total_comments == 55
|
||||
assert task.comments == []
|
||||
assert task.skip_field == ''
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,12 @@
|
||||
import 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,44 @@
|
||||
import json
|
||||
|
||||
struct Response[T] {
|
||||
time string
|
||||
cars []T
|
||||
}
|
||||
|
||||
struct Car {
|
||||
brand string
|
||||
power int
|
||||
}
|
||||
|
||||
fn string_to_response[T](text string) {
|
||||
result := json.decode([]Response[T], text) or { panic(err) }
|
||||
println(result)
|
||||
assert result.len == 2
|
||||
assert result[0].time == '12ms'
|
||||
assert result[0].cars[0].brand == 'Ferrari'
|
||||
assert result[0].cars[0].power == 232
|
||||
assert result[0].cars[1].brand == 'Lamborghini'
|
||||
assert result[0].cars[1].power == 345
|
||||
assert result[1].time == '12ms'
|
||||
assert result[1].cars[0].brand == 'Ferrari'
|
||||
assert result[1].cars[0].power == 232
|
||||
assert result[1].cars[1].brand == 'Lamborghini'
|
||||
assert result[1].cars[1].power == 345
|
||||
}
|
||||
|
||||
fn test_json_decode_with_generic_array() {
|
||||
text := '[
|
||||
{ "time":"12ms",
|
||||
"cars":[
|
||||
{"brand":"Ferrari","power":232},
|
||||
{"brand":"Lamborghini", "power":345}
|
||||
]},
|
||||
{ "time":"12ms",
|
||||
"cars":[
|
||||
{"brand":"Ferrari","power":232},
|
||||
{"brand":"Lamborghini", "power":345}
|
||||
]}
|
||||
]'
|
||||
|
||||
string_to_response[Car](text)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import json
|
||||
|
||||
struct Result[T] {
|
||||
ok bool
|
||||
result T
|
||||
}
|
||||
|
||||
struct User {
|
||||
id int
|
||||
username string
|
||||
}
|
||||
|
||||
struct Box[T] {
|
||||
items []T
|
||||
}
|
||||
|
||||
fn func[T]() !T {
|
||||
text := '{"ok": true, "result":{"id":37467243, "username": "ciao"}}'
|
||||
a := json.decode(Result[T], text)!
|
||||
return a.result
|
||||
}
|
||||
|
||||
fn string_decode[T](text string) !T {
|
||||
return json.decode(T, text)
|
||||
}
|
||||
|
||||
fn open_box[T](text string) ![]T {
|
||||
box := string_decode[Box[T]](text)!
|
||||
return box.items
|
||||
}
|
||||
|
||||
fn test_decode_with_generic_struct() {
|
||||
ret := func[User]()!
|
||||
println(ret)
|
||||
assert ret.id == 37467243
|
||||
assert ret.username == 'ciao'
|
||||
}
|
||||
|
||||
fn test_decode_with_nested_generic_struct() {
|
||||
text := '{"items":[{},{"id":37467243,"username":"ciao"},{}]}'
|
||||
items := open_box[User](text)!
|
||||
assert items.len == 3
|
||||
assert items[0].id == 0
|
||||
assert items[0].username == ''
|
||||
assert items[1].id == 37467243
|
||||
assert items[1].username == 'ciao'
|
||||
assert items[2].id == 0
|
||||
assert items[2].username == ''
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 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,104 @@
|
||||
import json
|
||||
|
||||
type Test = []bool | []int | []string | string
|
||||
|
||||
struct Some {
|
||||
t Test
|
||||
}
|
||||
|
||||
type GetBlockResult = string | GetBlockResultB
|
||||
|
||||
struct GetBlockResponse {
|
||||
result GetBlockResult
|
||||
}
|
||||
|
||||
struct GetBlockResultB {
|
||||
hash string
|
||||
confirmations u64
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
struct ComplexValue {
|
||||
foo string
|
||||
}
|
||||
|
||||
type StringOrComplexValue = ComplexValue | string
|
||||
|
||||
fn test_json_decode_with_sumtype_struct_variant_without_type_field() {
|
||||
decoded := json.decode([]StringOrComplexValue, '["abc",{"foo":"bar"}]')!
|
||||
|
||||
assert decoded.len == 2
|
||||
assert decoded[0] == StringOrComplexValue('abc')
|
||||
assert decoded[1] == StringOrComplexValue(ComplexValue{
|
||||
foo: 'bar'
|
||||
})
|
||||
}
|
||||
|
||||
struct OtherComplexValue {
|
||||
bar string
|
||||
}
|
||||
|
||||
type AmbiguousStringOrComplexValue = ComplexValue | OtherComplexValue | string
|
||||
|
||||
fn test_json_decode_with_sumtype_ambiguous_struct_variant_without_type_field_errors() {
|
||||
json.decode([]AmbiguousStringOrComplexValue, '[{"foo":"bar"}]') or {
|
||||
assert err.msg().contains('cannot decode')
|
||||
assert err.msg().contains('AmbiguousStringOrComplexValue')
|
||||
return
|
||||
}
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_json_decode_with_sumtype_struct_field_without_type_field() {
|
||||
decoded := json.decode(GetBlockResponse,
|
||||
'{ "result": { "hash": "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", "confirmations": 743970 } }')!
|
||||
|
||||
assert decoded.result == GetBlockResult(GetBlockResultB{
|
||||
hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09'
|
||||
confirmations: 743970
|
||||
})
|
||||
}
|
||||
|
||||
type ApiDetails = string | map[string]string
|
||||
|
||||
fn test_json_decode_with_sumtype_map_variant_without_type_field() {
|
||||
api_docs_file := r'
|
||||
[
|
||||
{
|
||||
"CloseHandle": [
|
||||
"https://learn.microsoft.com/windows/win32/api/handleapi/nf-handleapi-closehandle",
|
||||
"Closes an open object handle.",
|
||||
"The \n<b>CloseHandle</b> function ...",
|
||||
{
|
||||
"hObject": "A valid handle to an open object."
|
||||
},
|
||||
{},
|
||||
"If the function succeeds, ..."
|
||||
]
|
||||
}
|
||||
]
|
||||
'
|
||||
decoded := json.decode([]map[string][]ApiDetails, api_docs_file)!
|
||||
close_handle := decoded[0]['CloseHandle'] or { panic('missing CloseHandle docs') }
|
||||
|
||||
assert close_handle.len == 6
|
||||
assert close_handle[0] == ApiDetails('https://learn.microsoft.com/windows/win32/api/handleapi/nf-handleapi-closehandle')
|
||||
assert close_handle[3] == ApiDetails({
|
||||
'hObject': 'A valid handle to an open object.'
|
||||
})
|
||||
assert close_handle[4] == ApiDetails(map[string]string{})
|
||||
assert close_handle[5] == ApiDetails('If the function succeeds, ...')
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
|
||||
struct Json3 {
|
||||
embed f64
|
||||
}
|
||||
|
||||
struct Json2 {
|
||||
Json3
|
||||
inner []f64
|
||||
}
|
||||
|
||||
struct Json {
|
||||
Json2
|
||||
test f64
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
str := '{"inner": [1, 2, 3, 4, 5],"test": 1.0, "embed": 2.0}'
|
||||
data := json.decode(Json, str) or {
|
||||
eprintln('Failed to decode json, error: ${err}')
|
||||
return
|
||||
}
|
||||
println(data)
|
||||
assert data.inner.len == 5
|
||||
assert data.inner[0] == 1.0
|
||||
assert data.inner[4] == 5.0
|
||||
assert data.test == 1.0
|
||||
assert data.embed == 2.0
|
||||
|
||||
assert dump(json.encode(data)) == '{"embed":2,"inner":[1,2,3,4,5],"test":1}'
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import json
|
||||
|
||||
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_json_encode_array_of_references_with_nil() {
|
||||
value := [
|
||||
ref_array_item(1, false),
|
||||
ref_array_item(0, true),
|
||||
ref_array_item(2, false),
|
||||
]
|
||||
assert json.encode(value) == '[{"a":1},null,{"a":2}]'
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// vtest vflags: -w
|
||||
module main
|
||||
|
||||
import json
|
||||
|
||||
struct Test {
|
||||
id ?string = none
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
assert json.encode(Test{}) == '{}'
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import json
|
||||
|
||||
struct Json2 {
|
||||
inner []f64
|
||||
}
|
||||
|
||||
struct Json {
|
||||
Json2
|
||||
test f64
|
||||
}
|
||||
|
||||
struct Article {
|
||||
title string
|
||||
description string
|
||||
}
|
||||
|
||||
struct ArticlePlus {
|
||||
Article
|
||||
perex string
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
str := '{"inner":[1,2,3,4,5],"test":1.2}'
|
||||
data := json.decode(Json, str) or {
|
||||
eprintln('Failed to decode json, error: ${err}')
|
||||
return
|
||||
}
|
||||
println(data)
|
||||
assert data.inner.len == 5
|
||||
assert data.inner[0] == 1.0
|
||||
assert data.inner[4] == 5.0
|
||||
assert data.test == 1.2
|
||||
|
||||
data_json := json.encode(data)
|
||||
dump(data_json)
|
||||
assert data_json == str
|
||||
}
|
||||
|
||||
fn test_encode_array_of_embedded_structs() {
|
||||
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,135 @@
|
||||
import json
|
||||
|
||||
@[json_as_number]
|
||||
pub enum MessageType {
|
||||
error = 1
|
||||
warning = 2
|
||||
info = 3
|
||||
log = 4
|
||||
}
|
||||
|
||||
pub enum MessageType2 {
|
||||
error = 1
|
||||
warning = 2
|
||||
info = 3
|
||||
log = 4
|
||||
}
|
||||
|
||||
enum TestEnum {
|
||||
one = 1
|
||||
two
|
||||
}
|
||||
|
||||
type TestAlias = TestEnum
|
||||
type TestSum = TestEnum | string
|
||||
type TestSum2 = MessageType | string
|
||||
type TestAliasAttr = MessageType
|
||||
|
||||
struct TestStruct {
|
||||
test []TestEnum
|
||||
test2 TestEnum
|
||||
test3 TestAlias
|
||||
test4 TestSum
|
||||
test5 MessageType
|
||||
}
|
||||
|
||||
struct TestStruct2 {
|
||||
a TestAliasAttr
|
||||
b TestSum2
|
||||
c TestSum2
|
||||
}
|
||||
|
||||
struct Test {
|
||||
ab ?int
|
||||
a ?MessageType
|
||||
}
|
||||
|
||||
struct Test2 {
|
||||
a ?MessageType2
|
||||
}
|
||||
|
||||
type TSum = MessageType | string
|
||||
type TSum2 = MessageType2 | string
|
||||
|
||||
struct Test3 {
|
||||
a ?TSum
|
||||
}
|
||||
|
||||
struct Test4 {
|
||||
a ?TSum2
|
||||
}
|
||||
|
||||
fn test_encode_with_enum() {
|
||||
out := json.encode(TestStruct{
|
||||
test: [TestEnum.one, TestEnum.one]
|
||||
test2: TestEnum.two
|
||||
test3: TestEnum.one
|
||||
test4: TestEnum.two
|
||||
test5: .log
|
||||
})
|
||||
assert out == '{"test":["one","one"],"test2":"two","test3":"one","test4":"two","test5":4}'
|
||||
}
|
||||
|
||||
fn test_encode_direct_enum() {
|
||||
assert json.encode(TestEnum.one) == '"one"'
|
||||
}
|
||||
|
||||
fn test_encode_alias_and_sumtype() {
|
||||
assert json.decode(TestStruct,
|
||||
'{"test":["one","one"],"test2":"two","test3": "one", "test4": "two", "test5":4}')! == TestStruct{
|
||||
test: [.one, .one]
|
||||
test2: .two
|
||||
test3: TestAlias(.one)
|
||||
test4: TestSum('two')
|
||||
test5: .log
|
||||
}
|
||||
}
|
||||
|
||||
fn test_enum_attr() {
|
||||
assert dump(json.encode(MessageType.log)) == '4'
|
||||
assert dump(json.encode(MessageType.error)) == '1'
|
||||
}
|
||||
|
||||
fn test_enum_attr_decode() {
|
||||
assert json.decode(TestStruct2, '{"a": 1, "b":4, "c": "test"}')! == TestStruct2{
|
||||
a: .error
|
||||
b: MessageType.log
|
||||
c: 'test'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_enum_attr_encode() {
|
||||
assert json.encode(TestStruct2{
|
||||
a: .error
|
||||
b: MessageType.log
|
||||
c: 'test'
|
||||
}) == '{"a":1,"b":4,"c":"test"}'
|
||||
}
|
||||
|
||||
fn test_option_enum() {
|
||||
assert dump(json.encode(Test{none, none})) == '{}'
|
||||
assert dump(json.encode(Test{none, MessageType.log})) == '{"a":4}'
|
||||
t := dump(json.decode(Test, '{"a":4}')!)
|
||||
assert t.ab == none
|
||||
assert t.a? == .log
|
||||
|
||||
t2 := dump(json.decode(Test, '{"a":null}')!)
|
||||
assert t2.a == none
|
||||
|
||||
assert json.encode(Test2{none}) == '{}'
|
||||
assert dump(json.encode(Test2{MessageType2.log})) == '{"a":"log"}'
|
||||
z := dump(json.decode(Test2, '{"a":"log"}')!)
|
||||
assert z.a? == .log
|
||||
a := dump(json.decode(Test2, '{"a": null}')!)
|
||||
assert a.a == none
|
||||
}
|
||||
|
||||
fn test_option_sumtype_enum() {
|
||||
assert dump(json.encode(Test3{none})) == '{}'
|
||||
assert dump(json.encode(Test3{ a: 'foo' })) == '{"a":"foo"}'
|
||||
assert dump(json.encode(Test3{ a: MessageType.warning })) == '{"a":2}'
|
||||
|
||||
assert dump(json.encode(Test4{none})) == '{}'
|
||||
assert dump(json.encode(Test4{ a: 'foo' })) == '{"a":"foo"}'
|
||||
assert dump(json.encode(Test4{ a: MessageType2.warning })) == '{"a":"warning"}'
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import json
|
||||
|
||||
struct Wrap[T] {
|
||||
val T
|
||||
}
|
||||
|
||||
fn encode[T](value T) string {
|
||||
return json.encode(Wrap[T]{
|
||||
val: value
|
||||
})
|
||||
}
|
||||
|
||||
fn test_json_encode_generic_fixed_array_regression() {
|
||||
assert encode('x') == '{"val":"x"}'
|
||||
assert encode([1, 2]!) == '{"val":[1,2]}'
|
||||
}
|
||||
|
||||
fn test_json_encode_generic_fixed_array_regression_reversed() {
|
||||
assert encode([1, 2]!) == '{"val":[1,2]}'
|
||||
assert encode('x') == '{"val":"x"}'
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import json
|
||||
|
||||
struct Test {
|
||||
optional_string ?string
|
||||
optional_array ?[]string
|
||||
optional_struct_array ?[]string
|
||||
optional_map ?map[string]string
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
test := Test{}
|
||||
encoded := json.encode(test)
|
||||
assert dump(encoded) == '{}'
|
||||
|
||||
test2 := Test{
|
||||
optional_map: {
|
||||
'foo': 'bar'
|
||||
}
|
||||
}
|
||||
encoded2 := json.encode(test2)
|
||||
assert dump(encoded2) == '{"optional_map":{"foo":"bar"}}'
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import json
|
||||
|
||||
struct Test {
|
||||
field MySumType
|
||||
}
|
||||
|
||||
type MyInt = int
|
||||
type MyString = string
|
||||
type MySumType = MyString | int | string
|
||||
|
||||
struct UnicodeString {
|
||||
emoji string
|
||||
}
|
||||
|
||||
fn test_alias_to_primitive() {
|
||||
mut test := Test{
|
||||
field: MyString('foo')
|
||||
}
|
||||
mut encoded := json.encode(test)
|
||||
assert dump(encoded) == '{"field":"foo"}'
|
||||
assert json.decode(Test, '{"field": "foo"}')!.field == MySumType('foo')
|
||||
|
||||
test = Test{
|
||||
field: 'foo'
|
||||
}
|
||||
encoded = json.encode(test)
|
||||
assert dump(encoded) == '{"field":"foo"}'
|
||||
assert json.decode(Test, '{"field":"foo"}')! == test
|
||||
|
||||
test = Test{
|
||||
field: 1
|
||||
}
|
||||
encoded = json.encode(test)
|
||||
assert dump(encoded) == '{"field":1}'
|
||||
assert json.decode(Test, '{"field":1}')! == test
|
||||
|
||||
mut test2 := MyString('foo')
|
||||
encoded = json.encode(test2)
|
||||
assert dump(encoded) == '"foo"'
|
||||
|
||||
mut test3 := MyInt(1000)
|
||||
encoded = json.encode(test3)
|
||||
assert dump(encoded) == '1000'
|
||||
}
|
||||
|
||||
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,31 @@
|
||||
import json
|
||||
|
||||
struct Number {
|
||||
min int
|
||||
max int
|
||||
}
|
||||
|
||||
pub struct Resp {
|
||||
pub:
|
||||
options []string @[omitempty]
|
||||
number &Number = unsafe { nil } @[omitempty]
|
||||
}
|
||||
|
||||
fn (r Resp) str() string {
|
||||
return json.encode(r)
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
r1 := Resp{
|
||||
options: ['first', 'second']
|
||||
}
|
||||
r2 := Resp{
|
||||
number: &Number{0, 0}
|
||||
}
|
||||
r3 := Resp{
|
||||
number: &Number{1, 2}
|
||||
}
|
||||
assert r1.str() == '{"options":["first","second"]}'
|
||||
assert r2.str() == '{"number":{"min":0,"max":0}}'
|
||||
assert r3.str() == '{"number":{"min":1,"max":2}}'
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import json
|
||||
|
||||
struct PostTag {
|
||||
id string
|
||||
parent ?&PostTag
|
||||
visibility string
|
||||
created_at string @[json: 'createdAt']
|
||||
metadata string @[raw]
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
new_post_tag := &PostTag{}
|
||||
assert json.encode(new_post_tag) == '{"id":"","visibility":"","createdAt":"","metadata":""}'
|
||||
|
||||
new_post_tag2 := PostTag{
|
||||
parent: new_post_tag
|
||||
}
|
||||
assert json.encode(new_post_tag2) == '{"id":"","parent":{"id":"","visibility":"","createdAt":"","metadata":""},"visibility":"","createdAt":"","metadata":""}'
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import json
|
||||
import x.json2
|
||||
|
||||
struct Foo {
|
||||
name string
|
||||
num ?int
|
||||
}
|
||||
|
||||
fn test_json_encode_struct_with_option_field() {
|
||||
f1 := Foo{
|
||||
name: 'hello'
|
||||
}
|
||||
ret1 := json.encode(f1)
|
||||
println(ret1)
|
||||
assert ret1 == '{"name":"hello"}'
|
||||
|
||||
ret2 := json2.encode(f1)
|
||||
println(ret2)
|
||||
assert ret2 == '{"name":"hello"}'
|
||||
|
||||
f2 := Foo{'hello', 22}
|
||||
ret3 := json.encode(f2)
|
||||
println(ret3)
|
||||
assert ret3 == '{"name":"hello","num":22}'
|
||||
|
||||
ret4 := json2.encode(f2)
|
||||
println(ret4)
|
||||
assert ret4 == '{"name":"hello","num":22}'
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import json
|
||||
|
||||
struct Test {
|
||||
optional_sumtype ?MySumtype
|
||||
}
|
||||
|
||||
type MySumtype = int | string
|
||||
|
||||
fn test_simple() {
|
||||
test := Test{}
|
||||
encoded := json.encode(test)
|
||||
assert dump(encoded) == '{}'
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
module main
|
||||
|
||||
import json
|
||||
|
||||
pub enum PlatformType {
|
||||
unknown
|
||||
osx
|
||||
ubuntu
|
||||
alpine
|
||||
}
|
||||
|
||||
pub enum CPUType {
|
||||
unknown
|
||||
intel
|
||||
arm
|
||||
intel32
|
||||
arm32
|
||||
}
|
||||
|
||||
@[heap]
|
||||
pub struct Node {
|
||||
pub:
|
||||
name string = 'mymachine'
|
||||
pub mut:
|
||||
platform PlatformType
|
||||
cputype CPUType
|
||||
done map[string]string
|
||||
environment map[string]string
|
||||
}
|
||||
|
||||
pub fn (mut node Node) save() ! {
|
||||
data := json.encode(node)
|
||||
dump(data)
|
||||
}
|
||||
|
||||
fn test_encode_with_mut_struct() {
|
||||
mut n := Node{
|
||||
platform: .osx
|
||||
cputype: .unknown
|
||||
}
|
||||
n.save() or { panic(err) }
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import json
|
||||
|
||||
struct User {
|
||||
name string
|
||||
}
|
||||
|
||||
struct MyStruct {
|
||||
user &User //
|
||||
users map[string]User
|
||||
users2 map[string]&User
|
||||
}
|
||||
|
||||
fn test_json_encode_with_ptr() {
|
||||
user := User{
|
||||
name: 'foo'
|
||||
}
|
||||
data := MyStruct{
|
||||
user: &user
|
||||
users: {
|
||||
'keyfoo': user
|
||||
}
|
||||
users2: {
|
||||
'keyfoo': &user
|
||||
}
|
||||
}
|
||||
|
||||
assert json.encode(data) == '{"user":{"name":"foo"},"users":{"keyfoo":{"name":"foo"}},"users2":{"keyfoo":{"name":"foo"}}}'
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import json
|
||||
|
||||
struct F64ArrayPayload {
|
||||
arr []f64
|
||||
}
|
||||
|
||||
fn test_encode_decode_struct_with_f64_array_roundtrips() ! {
|
||||
original := F64ArrayPayload{
|
||||
arr: [0.9716157205240175, 0.9336099585062241]
|
||||
}
|
||||
encoded := json.encode(original)
|
||||
assert encoded == '{"arr":[0.9716157205240175,0.9336099585062241]}'
|
||||
assert json.decode(F64ArrayPayload, encoded)! == original
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import json
|
||||
|
||||
struct Base {
|
||||
options Options
|
||||
profiles Profiles
|
||||
}
|
||||
|
||||
struct Options {
|
||||
cfg [6]u8
|
||||
}
|
||||
|
||||
struct Profiles {
|
||||
cfg [4][7]u8
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
a := json.encode(Base{})
|
||||
println(a)
|
||||
assert a.contains('"cfg":[[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,0,0]]')
|
||||
|
||||
b := json.decode(Base, a)!
|
||||
assert b.options.cfg.len == 6
|
||||
assert b.profiles.cfg.len == 4
|
||||
assert b.profiles.cfg[0].len == 7
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 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,16 @@
|
||||
import json
|
||||
|
||||
pub struct StructB {
|
||||
kind string
|
||||
value i32
|
||||
}
|
||||
|
||||
fn test_json_i32() {
|
||||
struct_b := json.decode(StructB, '{"kind": "Int32", "value": 100}')!
|
||||
assert struct_b == StructB{
|
||||
kind: 'Int32'
|
||||
value: 100
|
||||
}
|
||||
|
||||
assert json.encode(struct_b) == '{"kind":"Int32","value":100}'
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import json
|
||||
|
||||
struct Bar {
|
||||
name ?string @[json_null]
|
||||
}
|
||||
|
||||
struct Foo {
|
||||
name ?string @[json_null]
|
||||
age ?int @[json_null]
|
||||
text ?string
|
||||
other ?Bar
|
||||
other2 ?Bar @[json_null]
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
assert json.encode(Foo{}) == '{"name":null,"age":null,"other2":null}'
|
||||
assert json.encode(Foo{ name: '' }) == '{"name":"","age":null,"other2":null}'
|
||||
assert json.encode(Foo{ age: 10 }) == '{"name":null,"age":10,"other2":null}'
|
||||
assert json.encode(Foo{
|
||||
age: 10
|
||||
other2: Bar{
|
||||
name: none
|
||||
}
|
||||
}) == '{"name":null,"age":10,"other2":{"name":null}}'
|
||||
assert json.decode(Foo, json.encode(Foo{}))! == Foo{}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import json
|
||||
|
||||
fn q_and_a(mut db_json map[string][]string) {
|
||||
x := json.encode(db_json)
|
||||
assert x == '{}'
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
mut db_json := json.decode(map[string][]string, '{}')!
|
||||
assert db_json == {}
|
||||
q_and_a(mut db_json)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import json
|
||||
|
||||
pub struct MyStruct {
|
||||
pub mut:
|
||||
code int
|
||||
message string
|
||||
data string @[omitempty]
|
||||
data2 ?string @[omitempty]
|
||||
}
|
||||
|
||||
fn test_simple() {
|
||||
obj := MyStruct{
|
||||
code: 1
|
||||
message: 'yes'
|
||||
data2: 'a'
|
||||
}
|
||||
assert dump(json.encode(obj)) == '{"code":1,"message":"yes","data2":"a"}'
|
||||
}
|
||||
|
||||
fn test_none() {
|
||||
obj := MyStruct{
|
||||
code: 1
|
||||
}
|
||||
assert dump(json.encode(obj)) == '{"code":1,"message":""}'
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import json
|
||||
|
||||
struct OmitEmptyStruct {
|
||||
bug Struct @[omitempty]
|
||||
}
|
||||
|
||||
type MyAlias = string
|
||||
|
||||
struct OmitEmptyAlias {
|
||||
bug MyAlias @[omitempty]
|
||||
}
|
||||
|
||||
struct Struct {
|
||||
name string
|
||||
}
|
||||
|
||||
struct OmitEmptyMap {
|
||||
bug map[string]string @[omitempty]
|
||||
}
|
||||
|
||||
struct OmitEmptyArray {
|
||||
bug []string @[omitempty]
|
||||
}
|
||||
|
||||
type MySum = int | string
|
||||
|
||||
struct OmitEmptySumType {
|
||||
bug MySum @[omitempty]
|
||||
}
|
||||
|
||||
struct FNumStruct {
|
||||
f_num f64
|
||||
}
|
||||
|
||||
struct OmitEmptyFNumStruct {
|
||||
bug FNumStruct @[omitempty]
|
||||
}
|
||||
|
||||
fn test_struct() {
|
||||
test := OmitEmptyStruct{
|
||||
bug: Struct{}
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
dump(encoded)
|
||||
test2 := OmitEmptyStruct{
|
||||
bug: Struct{
|
||||
name: 'mybug'
|
||||
}
|
||||
}
|
||||
encoded2 := json.encode(test2)
|
||||
dump(encoded2)
|
||||
}
|
||||
|
||||
fn test_fnum_struct() {
|
||||
test := OmitEmptyFNumStruct{
|
||||
bug: FNumStruct{}
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
dump(encoded)
|
||||
test2 := OmitEmptyFNumStruct{
|
||||
bug: FNumStruct{
|
||||
f_num: 1.5
|
||||
}
|
||||
}
|
||||
encoded2 := json.encode(test2)
|
||||
dump(encoded2)
|
||||
}
|
||||
|
||||
fn test_alias() {
|
||||
test := OmitEmptyAlias{
|
||||
bug: ''
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
dump(encoded)
|
||||
test2 := OmitEmptyAlias{
|
||||
bug: 'foo'
|
||||
}
|
||||
encoded2 := json.encode(test2)
|
||||
dump(encoded2)
|
||||
}
|
||||
|
||||
fn test_map() {
|
||||
test := OmitEmptyMap{
|
||||
bug: {}
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
dump(encoded)
|
||||
test2 := OmitEmptyMap{
|
||||
bug: {
|
||||
'foo': 'bar'
|
||||
}
|
||||
}
|
||||
encoded2 := json.encode(test2)
|
||||
dump(encoded2)
|
||||
}
|
||||
|
||||
fn test_sumtype() {
|
||||
test := OmitEmptySumType{}
|
||||
encoded := json.encode(test)
|
||||
dump(encoded)
|
||||
test2 := OmitEmptySumType{
|
||||
bug: 1
|
||||
}
|
||||
encoded2 := json.encode(test2)
|
||||
dump(encoded2)
|
||||
}
|
||||
|
||||
fn test_array() {
|
||||
test := OmitEmptyArray{
|
||||
bug: []
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
dump(encoded)
|
||||
test2 := OmitEmptyArray{
|
||||
bug: ['1']
|
||||
}
|
||||
encoded2 := json.encode(test2)
|
||||
dump(encoded2)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 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,46 @@
|
||||
import json
|
||||
|
||||
struct Struct {
|
||||
a int
|
||||
}
|
||||
|
||||
struct Test {
|
||||
a ?int
|
||||
b ?string
|
||||
c ?Struct
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
a := json.decode(Test, '{"a": 1, "b": "foo"}')!
|
||||
dump(a)
|
||||
|
||||
assert a.a != none
|
||||
assert a.b != none
|
||||
|
||||
b := json.decode(Test, '{"a": 1}')!
|
||||
dump(b)
|
||||
assert b.a != none
|
||||
assert b.b == none
|
||||
|
||||
c := json.decode(Test, '{"a": 1, "b": null}')!
|
||||
dump(b)
|
||||
assert c.a != none
|
||||
assert c.b == none
|
||||
|
||||
d := json.decode(Test, '{"a": null, "b": null}')!
|
||||
dump(d)
|
||||
assert d.a == none
|
||||
assert d.b == none
|
||||
|
||||
e := json.decode(Test, '{"a": null, "b": null, "c": null}')!
|
||||
dump(e)
|
||||
assert e.a == none
|
||||
assert e.b == none
|
||||
assert e.c == none
|
||||
|
||||
f := json.decode(Test, '{"a": null, "b": null, "c": {"a":1}}')!
|
||||
dump(f)
|
||||
assert f.a == none
|
||||
assert f.b == none
|
||||
assert f.c != none
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import json
|
||||
|
||||
pub struct Dto {
|
||||
pub:
|
||||
key string @[raw]
|
||||
key2 string @[raw]
|
||||
data ?string @[raw]
|
||||
optional ?string @[raw]
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
raw_json := '{
|
||||
"key": [1, 2, "test"],
|
||||
"key2": { "test": 1 },
|
||||
"data": { "test": 1 },
|
||||
"optional": "test"
|
||||
}'
|
||||
|
||||
dto := json.decode(Dto, raw_json)!
|
||||
|
||||
println(dto)
|
||||
assert dto.data? == '{"test":1}'
|
||||
}
|
||||
|
||||
fn test_none() {
|
||||
raw_json := '{
|
||||
"key": [1, 2, "test"],
|
||||
"optional": "test"
|
||||
}'
|
||||
|
||||
dto := json.decode(Dto, raw_json)!
|
||||
|
||||
println(dto)
|
||||
assert dto.data == none
|
||||
assert dto.optional? == '"test"'
|
||||
}
|
||||
|
||||
fn test_null() {
|
||||
raw_json := '{
|
||||
"key": [1, 2, "test"],
|
||||
"key2": null,
|
||||
"data": null,
|
||||
"optional": "test"
|
||||
}'
|
||||
|
||||
dto := json.decode(Dto, raw_json)!
|
||||
|
||||
println(dto)
|
||||
assert dto.key2 == 'null'
|
||||
assert dto.data? == 'null'
|
||||
}
|
||||
|
||||
fn test_not_set() {
|
||||
raw_json := '{
|
||||
}'
|
||||
|
||||
dto := json.decode(Dto, raw_json)!
|
||||
|
||||
println(dto)
|
||||
assert dto.data == none
|
||||
assert dto.optional == none
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import json
|
||||
|
||||
pub struct SomeStruct {
|
||||
pub mut:
|
||||
test ?string
|
||||
}
|
||||
|
||||
pub struct MyStruct {
|
||||
pub mut:
|
||||
result ?SomeStruct
|
||||
id string
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
a := MyStruct{
|
||||
id: 'some id'
|
||||
result: SomeStruct{}
|
||||
}
|
||||
encoded_string := json.encode(a)
|
||||
assert encoded_string == '{"result":{},"id":"some id"}'
|
||||
test := json.decode(MyStruct, encoded_string)!
|
||||
assert test == a
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import json
|
||||
|
||||
pub struct MyStruct {
|
||||
pub mut:
|
||||
valuea int
|
||||
}
|
||||
|
||||
pub struct MyStruct2 {
|
||||
pub mut:
|
||||
valuea int
|
||||
valueb ?MyStruct
|
||||
}
|
||||
|
||||
struct Node {
|
||||
location NodeLocation @[json: 'loc']
|
||||
}
|
||||
|
||||
struct NodeLocation {
|
||||
source_file ?SourceFile @[json: 'includedFrom']
|
||||
}
|
||||
|
||||
struct SourceFile {
|
||||
path string @[json: 'file']
|
||||
}
|
||||
|
||||
fn test_encode_decode() {
|
||||
assert json.encode(MyStruct2{ valuea: 1 }) == '{"valuea":1}'
|
||||
|
||||
assert json.decode(MyStruct2, '{"valuea": 1}')! == MyStruct2{
|
||||
valuea: 1
|
||||
valueb: none
|
||||
}
|
||||
}
|
||||
|
||||
fn test_encode_decode2() {
|
||||
assert json.encode(MyStruct2{ valuea: 1, valueb: none }) == '{"valuea":1}'
|
||||
|
||||
assert json.decode(MyStruct2, '{"valuea": 1}')! == MyStruct2{
|
||||
valuea: 1
|
||||
valueb: none
|
||||
}
|
||||
}
|
||||
|
||||
fn test_encode_decode3() {
|
||||
assert json.encode(MyStruct2{
|
||||
valuea: 1
|
||||
valueb: MyStruct{
|
||||
valuea: 123
|
||||
}
|
||||
}) == '{"valuea":1,"valueb":{"valuea":123}}'
|
||||
|
||||
assert json.decode(MyStruct2, '{"valuea": 1}')! == MyStruct2{
|
||||
valuea: 1
|
||||
valueb: none
|
||||
}
|
||||
}
|
||||
|
||||
fn test_main() {
|
||||
node := json.decode(Node, '{"loc": { "includedFrom": { "file": "/bin/foo" } } }')!
|
||||
|
||||
source_file := node.location.source_file or {
|
||||
SourceFile{
|
||||
path: '-'
|
||||
}
|
||||
}
|
||||
|
||||
assert source_file.path == '/bin/foo'
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
module main
|
||||
|
||||
import 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,52 @@
|
||||
import json
|
||||
|
||||
struct TestOptionalRawString {
|
||||
id int
|
||||
data ?string @[raw]
|
||||
}
|
||||
|
||||
struct TestRawStringifiedObject {
|
||||
metadata string @[raw]
|
||||
}
|
||||
|
||||
fn test_raw_opt() {
|
||||
test := TestOptionalRawString{
|
||||
id: 1
|
||||
data: 't
|
||||
e
|
||||
s
|
||||
t'
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
assert json.decode(TestOptionalRawString, encoded)!.data? == r'"t\ne\ns\nt"'
|
||||
}
|
||||
|
||||
fn test_raw_none() {
|
||||
test := TestOptionalRawString{
|
||||
id: 1
|
||||
data: none
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
r := json.decode(TestOptionalRawString, encoded)!.data
|
||||
assert r == none
|
||||
}
|
||||
|
||||
fn test_raw_empty_string() {
|
||||
test := TestOptionalRawString{
|
||||
id: 1
|
||||
data: ''
|
||||
}
|
||||
encoded := json.encode(test)
|
||||
r := json.decode(TestOptionalRawString, encoded)!.data or { 'z' }
|
||||
assert r == '""'
|
||||
}
|
||||
|
||||
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,46 @@
|
||||
import json
|
||||
|
||||
pub struct MyStruct[T] {
|
||||
pub mut:
|
||||
result ?T
|
||||
id string
|
||||
}
|
||||
|
||||
fn test_gn_struct_string() ! {
|
||||
a := MyStruct[string]{
|
||||
result: 'test'
|
||||
id: 'some id'
|
||||
}
|
||||
|
||||
encoded_string := json.encode(a)
|
||||
dump(encoded_string)
|
||||
test := json.decode(MyStruct[string], encoded_string)!
|
||||
dump(test)
|
||||
assert a == test
|
||||
}
|
||||
|
||||
fn test_gn_struct_int() ! {
|
||||
a := MyStruct[int]{
|
||||
result: 1
|
||||
id: 'some id'
|
||||
}
|
||||
|
||||
encoded_string := json.encode(a)
|
||||
dump(encoded_string)
|
||||
test := json.decode(MyStruct[int], encoded_string)!
|
||||
dump(test)
|
||||
assert a == test
|
||||
}
|
||||
|
||||
fn test_gn_struct_f64() ! {
|
||||
a := MyStruct[f64]{
|
||||
result: 1.2
|
||||
id: 'some id'
|
||||
}
|
||||
|
||||
encoded_string := json.encode(a)
|
||||
dump(encoded_string)
|
||||
test := json.decode(MyStruct[f64], encoded_string)!
|
||||
dump(test)
|
||||
assert a == test
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import 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
|
||||
}
|
||||
|
||||
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"}'
|
||||
}
|
||||
|
||||
type Value = string | i32
|
||||
|
||||
struct Node {
|
||||
value Value
|
||||
}
|
||||
|
||||
fn test_sum_types_with_i32() {
|
||||
data1 := json.encode([Node{i32(128)}, Node{'mystring'}])
|
||||
assert data1 == '[{"value":128},{"value":"mystring"}]'
|
||||
|
||||
node := json.decode([]Node, data1) or {
|
||||
println(err)
|
||||
assert false
|
||||
return
|
||||
}
|
||||
assert node.len == 2
|
||||
assert node[0].value == Value(i32(128))
|
||||
assert node[1].value == Value('mystring')
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
import json
|
||||
import time
|
||||
|
||||
enum JobTitle {
|
||||
manager
|
||||
executive
|
||||
worker
|
||||
}
|
||||
|
||||
struct Employee {
|
||||
name string
|
||||
age int
|
||||
salary f32
|
||||
title JobTitle
|
||||
}
|
||||
|
||||
fn test_simple() {
|
||||
x := Employee{'Peter', 28, 95000.5, .worker}
|
||||
s := json.encode(x)
|
||||
// eprintln('Employee x: ${s}')
|
||||
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":"worker"}'
|
||||
y := json.decode(Employee, s)!
|
||||
// eprintln('Employee y: ${y}')
|
||||
assert y.name == 'Peter'
|
||||
assert y.age == 28
|
||||
assert y.salary == 95000.5
|
||||
assert y.title == .worker
|
||||
}
|
||||
|
||||
const currency_id = 'cconst'
|
||||
|
||||
struct Price {
|
||||
net f64
|
||||
currency_id string = currency_id @[json: currencyId]
|
||||
}
|
||||
|
||||
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'
|
||||
}]
|
||||
}
|
||||
|
||||
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 // 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,
|
||||
]
|
||||
}
|
||||
// eprintln('Game: ${game}')
|
||||
|
||||
enc := json.encode(game)
|
||||
// eprintln('Encoded Game: ${enc}')
|
||||
|
||||
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},"cat","Stool",{"_type":"Time","value":${t.unix()}}]}'
|
||||
|
||||
dec := json.decode(SomeGame, enc)!
|
||||
// eprintln('Decoded Game: ${dec}')
|
||||
|
||||
assert game.title == dec.title
|
||||
assert game.player == dec.player
|
||||
assert (game.other[2] as Animal) == .cat
|
||||
assert dec.other[2] == Entity('cat')
|
||||
assert (game.other[4] as time.Time).unix() == (dec.other[4] as time.Time).unix()
|
||||
}
|
||||
|
||||
fn bar[T](payload string) !Bar { // ?T doesn't work currently
|
||||
result := json.decode(T, payload)!
|
||||
return result
|
||||
}
|
||||
|
||||
struct Bar {
|
||||
x string
|
||||
}
|
||||
|
||||
fn test_generic() {
|
||||
result := bar[Bar]('{"x":"test"}') or { Bar{} }
|
||||
assert result.x == 'test'
|
||||
}
|
||||
|
||||
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_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)!
|
||||
// println(u2)
|
||||
u := json.decode(User, s)!
|
||||
// println(u)
|
||||
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)
|
||||
// println(s)
|
||||
assert s.contains('"reg_date":1608621780')
|
||||
user2 := json.decode(User2, s)!
|
||||
assert user2.reg_date.str() == '2020-12-22 07:23:00'
|
||||
// println(user2)
|
||||
// println(user2.reg_date)
|
||||
}
|
||||
|
||||
fn test_decode_time_string_into_struct_field() {
|
||||
user := json.decode(User2, '{"age": 25, "reg_date": "2001-01-01"}')!
|
||||
assert user.reg_date.str() == '2001-01-01 00:00: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(usr)
|
||||
// println(out)
|
||||
assert out == expected
|
||||
// Test json.encode on mutable pointers
|
||||
assert usr.foo() == expected
|
||||
}
|
||||
|
||||
struct Color {
|
||||
space string
|
||||
point string @[raw]
|
||||
}
|
||||
|
||||
fn test_raw_json_field() {
|
||||
color := json.decode(Color, '{"space": "YCbCr", "point": {"Y": 123}}') or {
|
||||
// println('text')
|
||||
return
|
||||
}
|
||||
assert color.point == '{"Y":123}'
|
||||
assert color.space == 'YCbCr'
|
||||
}
|
||||
|
||||
fn test_bad_raw_json_field() {
|
||||
color := json.decode(Color, '{"space": "YCbCr"}') or {
|
||||
// println('text')
|
||||
return
|
||||
}
|
||||
assert color.point == ''
|
||||
assert color.space == 'YCbCr'
|
||||
}
|
||||
|
||||
struct City {
|
||||
name string
|
||||
}
|
||||
|
||||
struct Country {
|
||||
cities []City
|
||||
name string
|
||||
}
|
||||
|
||||
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'
|
||||
// println(country.cities)
|
||||
}
|
||||
|
||||
fn test_encode_map() {
|
||||
expected := '{"one":1,"two":2,"three":3,"four":4}'
|
||||
numbers := {
|
||||
'one': 1
|
||||
'two': 2
|
||||
'three': 3
|
||||
'four': 4
|
||||
}
|
||||
out := json.encode(numbers)
|
||||
// println(out)
|
||||
assert out == expected
|
||||
assert json.encode_pretty(numbers) == '{
|
||||
"one": 1,
|
||||
"two": 2,
|
||||
"three": 3,
|
||||
"four": 4
|
||||
}'
|
||||
}
|
||||
|
||||
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}')!
|
||||
// println(out)
|
||||
assert out == expected
|
||||
}
|
||||
|
||||
struct Data {
|
||||
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)
|
||||
// println(out)
|
||||
assert out == data_expected
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
type ID = string
|
||||
type GG = int
|
||||
|
||||
struct Message {
|
||||
id ID
|
||||
ij GG
|
||||
}
|
||||
|
||||
fn test_decode_alias_struct() {
|
||||
msg := json.decode(Message, '{"id": "118499178790780929"}')!
|
||||
// hacky way of comparing aliased strings
|
||||
assert msg.id.str() == '118499178790780929'
|
||||
assert msg.ij.str() == '0'
|
||||
}
|
||||
|
||||
fn test_encode_alias_struct() {
|
||||
expected := '{"id":"118499178790780929","ij":999998888}'
|
||||
msg := Message{'118499178790780929', 999998888}
|
||||
out := json.encode(msg)
|
||||
assert out == expected
|
||||
}
|
||||
|
||||
struct List {
|
||||
id int
|
||||
items []string
|
||||
}
|
||||
|
||||
fn test_list() {
|
||||
list := json.decode(List, '{"id": 1, "items": ["1", "2"]}')!
|
||||
assert list.id == 1
|
||||
assert list.items == ['1', '2']
|
||||
}
|
||||
|
||||
fn test_list_no_id() {
|
||||
list := json.decode(List, '{"items": ["1", "2"]}')!
|
||||
assert list.id == 0
|
||||
assert list.items == ['1', '2']
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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}' == '{}'
|
||||
}
|
||||
|
||||
struct Foo2 {
|
||||
name string
|
||||
}
|
||||
|
||||
fn test_pretty() {
|
||||
foo := Foo2{'Bob'}
|
||||
assert json.encode_pretty(foo) == '{
|
||||
"name": "Bob"
|
||||
}'
|
||||
}
|
||||
|
||||
struct Foo3 {
|
||||
name string
|
||||
age int @[omitempty]
|
||||
}
|
||||
|
||||
fn test_omit_empty() {
|
||||
foo := Foo3{'Bob', 0}
|
||||
assert json.encode_pretty(foo) == '{
|
||||
"name": "Bob"
|
||||
}'
|
||||
// println('omitempty:')
|
||||
// println(json.encode_pretty(foo))
|
||||
}
|
||||
|
||||
fn test_encode_struct_expression() {
|
||||
assert json.encode(Foo2{'Foo'}) == '{"name":"Foo"}'
|
||||
assert json.encode_pretty(Foo2{'Bar'}) == '{
|
||||
"name": "Bar"
|
||||
}'
|
||||
}
|
||||
|
||||
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{}))
|
||||
// println(ret)
|
||||
assert ret == '{"value":0,"_type":"GPScale"}'
|
||||
}
|
||||
|
||||
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 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 {}
|
||||
|
||||
struct Association {
|
||||
association &Association = unsafe { nil }
|
||||
price APrice
|
||||
}
|
||||
|
||||
fn test_encoding_struct_with_pointers() {
|
||||
value := Association{
|
||||
association: &Association{
|
||||
price: APrice{}
|
||||
}
|
||||
price: APrice{}
|
||||
}
|
||||
assert json.encode(value) == '{"association":{"price":{}},"price":{}}'
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user