mirror of
https://github.com/vlang/v.git
synced 2025-09-13 22:42:26 +03:00
replace json2 encoder and rewrite tests
This commit is contained in:
parent
d31aaecc42
commit
5d4b91b03e
19 changed files with 567 additions and 1102 deletions
|
@ -28,3 +28,8 @@ pub fn (mut result Integer) from_json_number(raw_number string) ! {
|
|||
result = result * integer_from_int(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// to_json implements a custom encoder for json2
|
||||
pub fn (result Integer) to_json() string {
|
||||
return result.str()
|
||||
}
|
|
@ -34,3 +34,8 @@ pub fn (mut t Time) from_json_string(raw_string string) ! {
|
|||
|
||||
return error('Expected iso8601/rfc3339/unix time but got: ${raw_string}')
|
||||
}
|
||||
|
||||
// to_json implements a custom encoder for json2 (rfc3339)
|
||||
pub fn (t Time) to_json() string {
|
||||
return '"' + t.format_rfc3339() + '"'
|
||||
}
|
5
vlib/x/json2/constants.v
Normal file
5
vlib/x/json2/constants.v
Normal file
|
@ -0,0 +1,5 @@
|
|||
module json2
|
||||
|
||||
const true_string = 'true'
|
||||
const false_string = 'false'
|
||||
const null_string = 'null'
|
|
@ -1,98 +0,0 @@
|
|||
module json2
|
||||
|
||||
import time
|
||||
|
||||
struct Count {
|
||||
mut:
|
||||
total int
|
||||
}
|
||||
|
||||
// get_total
|
||||
fn (mut count Count) get_total() int {
|
||||
return count.total
|
||||
}
|
||||
|
||||
// reset_total
|
||||
fn (mut count Count) reset_total() {
|
||||
count.total = 0
|
||||
}
|
||||
|
||||
// count_chars count json sizen without new encode
|
||||
fn (mut count Count) count_chars[T](val T) {
|
||||
$if val is $option {
|
||||
workaround := val
|
||||
if workaround != none {
|
||||
count.count_chars(val)
|
||||
}
|
||||
} $else $if T is string {
|
||||
count.chars_in_string(val)
|
||||
} $else $if T is $sumtype {
|
||||
$for v in val.variants {
|
||||
if val is v {
|
||||
count.count_chars(val)
|
||||
}
|
||||
}
|
||||
} $else $if T is $alias {
|
||||
// TODO
|
||||
} $else $if T is time.Time {
|
||||
count.total += 26 // "YYYY-MM-DDTHH:mm:ss.123Z"
|
||||
} $else $if T is $map {
|
||||
count.total++ // {
|
||||
for k, v in val {
|
||||
count.count_chars(k)
|
||||
count.total++ // :
|
||||
count.count_chars(v)
|
||||
}
|
||||
count.total++ // }
|
||||
} $else $if T is $array {
|
||||
count.total += 2 // []
|
||||
if val.len > 0 {
|
||||
for element in val {
|
||||
count.count_chars(element)
|
||||
}
|
||||
count.total += val.len - 1 // ,
|
||||
}
|
||||
} $else $if T is $struct {
|
||||
count.chars_in_struct(val)
|
||||
} $else $if T is $enum {
|
||||
count.count_chars(int(val))
|
||||
} $else $if T is $int {
|
||||
// TODO: benchmark
|
||||
mut abs_val := val
|
||||
if val < 0 {
|
||||
count.total++ // -
|
||||
abs_val = -val
|
||||
}
|
||||
for number_value := abs_val; number_value >= 1; number_value /= 10 {
|
||||
count.total++
|
||||
}
|
||||
if val == 0 {
|
||||
count.total++
|
||||
}
|
||||
} $else $if T is $float {
|
||||
// TODO
|
||||
} $else $if T is bool {
|
||||
if val {
|
||||
count.total += 4 // true
|
||||
} else {
|
||||
count.total += 5 // false
|
||||
}
|
||||
} $else {
|
||||
}
|
||||
}
|
||||
|
||||
// chars_in_struct
|
||||
fn (mut count Count) chars_in_struct[T](val T) {
|
||||
count.total += 2 // {}
|
||||
$for field in T.fields {
|
||||
// TODO: handle attributes
|
||||
count.total += field.name.len + 3 // "":
|
||||
workaround := val.$(field.name)
|
||||
count.count_chars(workaround)
|
||||
}
|
||||
}
|
||||
|
||||
// chars_in_string
|
||||
fn (mut count Count) chars_in_string(val string) {
|
||||
count.total += val.len + 2 // ""
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
module json2
|
||||
|
||||
import time
|
||||
|
||||
const fixed_time = time.new(
|
||||
year: 2022
|
||||
month: 3
|
||||
day: 11
|
||||
hour: 13
|
||||
minute: 54
|
||||
second: 25
|
||||
)
|
||||
|
||||
type StringAlias = string
|
||||
type BoolAlias = bool
|
||||
type IntAlias = int
|
||||
type TimeAlias = time.Time
|
||||
type StructAlias = StructType[int]
|
||||
type EnumAlias = Enumerates
|
||||
|
||||
type SumTypes = StructType[string] | []SumTypes | []string | bool | int | string | time.Time
|
||||
|
||||
enum Enumerates {
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e = 99
|
||||
f
|
||||
}
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
struct StructTypeOption[T] {
|
||||
mut:
|
||||
val ?T
|
||||
}
|
||||
|
||||
struct StructTypePointer[T] {
|
||||
mut:
|
||||
val &T
|
||||
}
|
||||
|
||||
fn count_test[T](value T) {
|
||||
mut count := Count{0}
|
||||
|
||||
count.count_chars(value)
|
||||
assert encode(value).len == count.get_total()
|
||||
}
|
||||
|
||||
fn test_empty() {
|
||||
count_test(map[string]string{})
|
||||
|
||||
count_test([]string{})
|
||||
|
||||
count_test(StructType[bool]{})
|
||||
|
||||
count_test(map[string]string{})
|
||||
}
|
||||
|
||||
fn test_types() {
|
||||
count_test(StructType[string]{})
|
||||
|
||||
count_test(StructType[string]{ val: '' })
|
||||
|
||||
count_test(StructType[string]{ val: 'abcd' })
|
||||
|
||||
count_test(StructType[bool]{ val: false })
|
||||
|
||||
count_test(StructType[bool]{ val: true })
|
||||
|
||||
count_test(StructType[int]{ val: 26 })
|
||||
|
||||
count_test(StructType[int]{ val: 1 })
|
||||
|
||||
count_test(StructType[int]{ val: -125 })
|
||||
|
||||
count_test(StructType[u64]{ val: u64(-1) })
|
||||
|
||||
count_test(StructType[time.Time]{})
|
||||
|
||||
count_test(StructType[time.Time]{ val: fixed_time })
|
||||
|
||||
count_test(StructType[StructType[int]]{
|
||||
val: StructType[int]{
|
||||
val: 1
|
||||
}
|
||||
})
|
||||
|
||||
count_test(StructType[Enumerates]{})
|
||||
count_test(StructType[Enumerates]{})
|
||||
count_test(StructType[Enumerates]{ val: Enumerates.f })
|
||||
count_test(StructType[[]int]{})
|
||||
count_test(StructType[[]int]{ val: [0] })
|
||||
count_test(StructType[[]int]{ val: [0, 1, 0, 2, 3, 2, 5, 1] })
|
||||
}
|
15
vlib/x/json2/custom.v
Normal file
15
vlib/x/json2/custom.v
Normal file
|
@ -0,0 +1,15 @@
|
|||
module json2
|
||||
|
||||
// implements encoding json, this is not validated so implementations must be correct
|
||||
pub interface JsonEncoder {
|
||||
// to_json returns a string containing an objects json representation
|
||||
to_json() string
|
||||
}
|
||||
|
||||
// Encodable is an interface, that allows custom implementations for encoding structs to their string based JSON representations.
|
||||
|
||||
@[deprecated: 'use `to_json` to implement `JsonEncoder` instead']
|
||||
@[deprecated_after: '2025-10-30']
|
||||
pub interface Encodable {
|
||||
json_str() string
|
||||
}
|
|
@ -16,6 +16,7 @@ struct OptAnyStruct[T] {
|
|||
fn test_values() {
|
||||
assert json.decode[AnyStruct[json2.Any]]('{"val":5}')!.val.int() == 5
|
||||
assert json.decode[OptAnyStruct[json2.Any]]('{}')!.val == none
|
||||
assert json.decode[OptAnyStruct[json2.Any]]('{"val":null}')!.val == none
|
||||
assert json.decode[AnyStruct[[]json2.Any]]('{"val":[5,10]}')!.val.map(it.int()) == [
|
||||
5,
|
||||
10,
|
||||
|
@ -23,7 +24,7 @@ fn test_values() {
|
|||
// assert json.decode[OptAnyArrStruct]('{"val":[5,null,10]}')!.val == [?json2.Any(5),json.Null{},10] // skipped because test still fails even though they're the same
|
||||
|
||||
assert json2.encode[AnyStruct[json2.Any]](AnyStruct[json2.Any]{json2.Any(5)}) == '{"val":5}'
|
||||
assert json2.encode[OptAnyStruct[json2.Any]](OptAnyStruct[json2.Any]{none}) == '{}'
|
||||
assert json2.encode[OptAnyStruct[json2.Any]](OptAnyStruct[json2.Any]{none}) == '{"val":null}'
|
||||
assert json2.encode[AnyStruct[[]json2.Any]](AnyStruct[[]json2.Any]{[json2.Any(5), 10]}) == '{"val":[5,10]}'
|
||||
// assert json2.encode[OptAnyArrStruct](OptAnyArrStruct{[?json2.Any(5),none,10]}) == '{"val":[5,null,10]}' // encode_array has not implemented optional arrays yet
|
||||
}
|
||||
|
|
|
@ -1,74 +1,5 @@
|
|||
import x.json2.decoder2 as json
|
||||
import x.json2
|
||||
import strings
|
||||
import time
|
||||
|
||||
struct StructType[T] {
|
||||
mut:
|
||||
val T
|
||||
}
|
||||
|
||||
fn test_json_string_characters() {
|
||||
assert json2.encode([u8(`/`)].bytestr()).bytes() == r'"\/"'.bytes()
|
||||
assert json2.encode([u8(`\\`)].bytestr()).bytes() == r'"\\"'.bytes()
|
||||
assert json2.encode([u8(`"`)].bytestr()).bytes() == r'"\""'.bytes()
|
||||
assert json2.encode([u8(`\n`)].bytestr()).bytes() == r'"\n"'.bytes()
|
||||
assert json2.encode(r'\n\r') == r'"\\n\\r"'
|
||||
assert json2.encode('\\n') == r'"\\n"'
|
||||
assert json2.encode(r'\n\r\b') == r'"\\n\\r\\b"'
|
||||
assert json2.encode(r'\"/').bytes() == r'"\\\"\/"'.bytes()
|
||||
|
||||
assert json2.encode(r'\n\r\b\f\t\\\"\/') == r'"\\n\\r\\b\\f\\t\\\\\\\"\\\/"'
|
||||
|
||||
assert json2.encode("fn main(){nprintln('Hello World! Helo \$a')\n}") == '"fn main(){nprintln(\'Hello World! Helo \$a\')\\n}"'
|
||||
assert json2.encode(' And when "\'s are in the string, along with # "') == '" And when \\"\'s are in the string, along with # \\""'
|
||||
assert json2.encode('a \\\nb') == r'"a \\\nb"'
|
||||
assert json2.encode('Name\tJosé\nLocation\tSF.') == '"Name\\tJosé\\nLocation\\tSF."'
|
||||
}
|
||||
|
||||
fn test_json_escape_low_chars() {
|
||||
esc := '\u001b'
|
||||
assert esc.len == 1
|
||||
text := json2.Any(esc)
|
||||
assert text.json_str() == r'"\u001b"'
|
||||
|
||||
assert json2.encode('\u000f') == r'"\u000f"'
|
||||
assert json2.encode('\u0020') == r'" "'
|
||||
assert json2.encode('\u0000') == r'"\u0000"'
|
||||
}
|
||||
|
||||
fn test_json_string() {
|
||||
text := json2.Any('te✔st')
|
||||
|
||||
assert text.json_str() == r'"te\u2714st"'
|
||||
assert json2.encode('te✔st') == r'"te\u2714st"'
|
||||
|
||||
boolean := json2.Any(true)
|
||||
assert boolean.json_str() == 'true'
|
||||
integer := json2.Any(int(-5))
|
||||
assert integer.json_str() == '-5'
|
||||
u64integer := json2.Any(u64(5000))
|
||||
assert u64integer.json_str() == '5000'
|
||||
i64integer := json2.Any(i64(-17))
|
||||
assert i64integer.json_str() == '-17'
|
||||
}
|
||||
|
||||
fn test_json_string_emoji() {
|
||||
text := json2.Any('🐈')
|
||||
assert text.json_str() == r'"🐈"'
|
||||
assert json2.Any('💀').json_str() == r'"💀"'
|
||||
|
||||
assert json2.encode('🐈') == r'"🐈"'
|
||||
assert json2.encode('💀') == r'"💀"'
|
||||
assert json2.encode('🐈💀') == r'"🐈💀"'
|
||||
}
|
||||
|
||||
fn test_json_string_non_ascii() {
|
||||
text := json2.Any('ひらがな')
|
||||
assert text.json_str() == r'"\u3072\u3089\u304c\u306a"'
|
||||
|
||||
assert json2.encode('ひらがな') == r'"\u3072\u3089\u304c\u306a"'
|
||||
}
|
||||
|
||||
fn test_utf8_strings_are_not_modified() {
|
||||
original := '{"s":"Schilddrüsenerkrankungen"}'
|
||||
|
@ -78,149 +9,3 @@ fn test_utf8_strings_are_not_modified() {
|
|||
assert json2.encode('ü') == '"ü"'
|
||||
assert json2.encode('Schilddrüsenerkrankungen') == '"Schilddrüsenerkrankungen"'
|
||||
}
|
||||
|
||||
fn test_encoder_unescaped_utf32() ! {
|
||||
jap_text := json2.Any('ひらがな')
|
||||
enc := json2.Encoder{
|
||||
escape_unicode: false
|
||||
}
|
||||
|
||||
mut sb := strings.new_builder(20)
|
||||
defer {
|
||||
unsafe { sb.free() }
|
||||
}
|
||||
|
||||
enc.encode_value(jap_text, mut sb)!
|
||||
|
||||
assert sb.str() == '"${jap_text}"'
|
||||
sb.go_back_to(0)
|
||||
|
||||
emoji_text := json2.Any('🐈')
|
||||
enc.encode_value(emoji_text, mut sb)!
|
||||
assert sb.str() == '"${emoji_text}"'
|
||||
|
||||
mut buf := []u8{cap: 14}
|
||||
|
||||
enc.encode_value('ひらがな', mut buf)!
|
||||
|
||||
assert buf.len == 14
|
||||
assert buf.bytestr() == '"ひらがな"'
|
||||
}
|
||||
|
||||
fn test_encoder_prettify() {
|
||||
obj := {
|
||||
'hello': json2.Any('world')
|
||||
'arr': [json2.Any('im a string'), [json2.Any('3rd level')]]
|
||||
'obj': {
|
||||
'map': json2.Any('map inside a map')
|
||||
}
|
||||
}
|
||||
enc := json2.Encoder{
|
||||
newline: `\n`
|
||||
newline_spaces_count: 2
|
||||
}
|
||||
mut sb := strings.new_builder(20)
|
||||
defer {
|
||||
unsafe { sb.free() }
|
||||
}
|
||||
enc.encode_value(obj, mut sb)!
|
||||
assert sb.str() == '{
|
||||
"hello": "world",
|
||||
"arr": [
|
||||
"im a string",
|
||||
[
|
||||
"3rd level"
|
||||
]
|
||||
],
|
||||
"obj": {
|
||||
"map": "map inside a map"
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
pub struct Test {
|
||||
val string
|
||||
}
|
||||
|
||||
fn test_encode_struct() {
|
||||
enc := json2.encode(Test{'hello!'})
|
||||
assert enc == '{"val":"hello!"}'
|
||||
}
|
||||
|
||||
pub struct Uri {
|
||||
protocol string
|
||||
path string
|
||||
}
|
||||
|
||||
pub fn (u Uri) json_str() string {
|
||||
return '"${u.protocol}://${u.path}"'
|
||||
}
|
||||
|
||||
fn test_encode_encodable() {
|
||||
assert json2.encode(Uri{'file', 'path/to/file'}) == '"file://path/to/file"'
|
||||
}
|
||||
|
||||
fn test_encode_array() {
|
||||
array_of_struct := [StructType[[]bool]{
|
||||
val: [false, true]
|
||||
}, StructType[[]bool]{
|
||||
val: [true, false]
|
||||
}]
|
||||
|
||||
assert json2.encode([1, 2, 3]) == '[1,2,3]'
|
||||
|
||||
assert json2.encode(array_of_struct) == '[{"val":[false,true]},{"val":[true,false]}]'
|
||||
}
|
||||
|
||||
fn test_encode_simple() {
|
||||
assert json2.encode('hello!') == '"hello!"'
|
||||
assert json2.encode(1) == '1'
|
||||
}
|
||||
|
||||
fn test_encode_value() {
|
||||
json_enc := json2.Encoder{
|
||||
newline: `\n`
|
||||
newline_spaces_count: 2
|
||||
escape_unicode: false
|
||||
}
|
||||
|
||||
mut manifest := map[string]json2.Any{}
|
||||
|
||||
manifest['server_path'] = json2.Any('new_path')
|
||||
manifest['last_updated'] = json2.Any('timestamp.format_ss()')
|
||||
manifest['from_source'] = json2.Any('from_source')
|
||||
|
||||
mut sb := strings.new_builder(64)
|
||||
mut buffer := []u8{}
|
||||
json_enc.encode_value(manifest, mut buffer)!
|
||||
|
||||
assert buffer.len > 0
|
||||
assert buffer == [u8(123), 10, 32, 32, 34, 115, 101, 114, 118, 101, 114, 95, 112, 97, 116,
|
||||
104, 34, 58, 32, 34, 110, 101, 119, 95, 112, 97, 116, 104, 34, 44, 10, 32, 32, 34, 108,
|
||||
97, 115, 116, 95, 117, 112, 100, 97, 116, 101, 100, 34, 58, 32, 34, 116, 105, 109, 101,
|
||||
115, 116, 97, 109, 112, 46, 102, 111, 114, 109, 97, 116, 95, 115, 115, 40, 41, 34, 44,
|
||||
10, 32, 32, 34, 102, 114, 111, 109, 95, 115, 111, 117, 114, 99, 101, 34, 58, 32, 34, 102,
|
||||
114, 111, 109, 95, 115, 111, 117, 114, 99, 101, 34, 10, 125]
|
||||
|
||||
sb.write(buffer)!
|
||||
|
||||
unsafe { buffer.free() }
|
||||
|
||||
assert sb.str() == r'{
|
||||
"server_path": "new_path",
|
||||
"last_updated": "timestamp.format_ss()",
|
||||
"from_source": "from_source"
|
||||
}'
|
||||
}
|
||||
|
||||
fn test_encode_time() {
|
||||
assert json2.encode({
|
||||
'bro': json2.Any(time.Time{})
|
||||
}) == '{"bro":"0000-00-00T00:00:00.000Z"}'
|
||||
|
||||
assert json2.encode({
|
||||
'bro': time.Time{}
|
||||
}) == '{"bro":"0000-00-00T00:00:00.000Z"}'
|
||||
|
||||
assert json2.encode(time.Time{}) == '"0000-00-00T00:00:00.000Z"'
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ fn test_simple() {
|
|||
name: 'João'
|
||||
}
|
||||
x := Employee{'Peter', 28, 95000.5, .worker, sub_employee}
|
||||
s := json2.encode[Employee](x)
|
||||
s := json2.encode[Employee](x, enum_as_int: true)
|
||||
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2,"sub_employee":{"name":"João","age":0,"salary":0,"title":0}}'
|
||||
|
||||
y := json.decode[Employee](s) or {
|
||||
|
@ -230,7 +230,7 @@ struct Foo2 {
|
|||
|
||||
fn test_pretty() {
|
||||
foo := Foo2{1, 2, 3, 4, -1, -2, -3, -4, true, 'abc', 'aliens'}
|
||||
assert json2.encode_pretty(foo) == '{
|
||||
assert json2.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"ux8": 1,
|
||||
"ux16": 2,
|
||||
"ux32": 3,
|
||||
|
@ -385,7 +385,7 @@ fn test_encode_decode_sumtype() {
|
|||
]
|
||||
}
|
||||
|
||||
enc := json2.encode(game)
|
||||
enc := json2.encode(game, enum_as_int: true)
|
||||
|
||||
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke"},"other":[{"tag":"Pen"},{"tag":"Cookie"},1,"Stool","${t.format_rfc3339()}"]}'
|
||||
}
|
||||
|
@ -412,8 +412,9 @@ fn test_option_instead_of_omit_empty() {
|
|||
foo := Foo31{
|
||||
name: 'Bob'
|
||||
}
|
||||
assert json2.encode_pretty(foo) == '{
|
||||
"name": "Bob"
|
||||
assert json2.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"name": "Bob",
|
||||
"age": null
|
||||
}'
|
||||
}
|
||||
|
||||
|
|
466
vlib/x/json2/encode.v
Normal file
466
vlib/x/json2/encode.v
Normal file
|
@ -0,0 +1,466 @@
|
|||
module json2
|
||||
|
||||
@[params]
|
||||
pub struct EncoderOptions {
|
||||
pub:
|
||||
prettify bool
|
||||
indent_string string = ' '
|
||||
newline_string string = '\n'
|
||||
|
||||
enum_as_int bool
|
||||
|
||||
escape_unicode bool
|
||||
}
|
||||
|
||||
struct Encoder {
|
||||
EncoderOptions
|
||||
mut:
|
||||
level int
|
||||
prefix string
|
||||
|
||||
output []u8 = []u8{cap: 2048}
|
||||
}
|
||||
|
||||
@[inline]
|
||||
fn workaround_cast[T](val voidptr) T {
|
||||
return *(&T(val))
|
||||
}
|
||||
|
||||
pub fn encode[T](val T, config EncoderOptions) string {
|
||||
mut encoder := Encoder{
|
||||
EncoderOptions: config
|
||||
}
|
||||
|
||||
encoder.encode_value(val)
|
||||
|
||||
return encoder.output.bytestr()
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_value[T](val T) {
|
||||
$if T.unaliased_typ is string {
|
||||
encoder.encode_string(workaround_cast[string](&val))
|
||||
} $else $if T.unaliased_typ is bool {
|
||||
encoder.encode_boolean(workaround_cast[bool](&val))
|
||||
} $else $if T.unaliased_typ is u8 {
|
||||
encoder.encode_number(workaround_cast[u8](&val))
|
||||
} $else $if T.unaliased_typ is u16 {
|
||||
encoder.encode_number(workaround_cast[u16](&val))
|
||||
} $else $if T.unaliased_typ is u32 {
|
||||
encoder.encode_number(workaround_cast[u32](&val))
|
||||
} $else $if T.unaliased_typ is u64 {
|
||||
encoder.encode_number(workaround_cast[u64](&val))
|
||||
} $else $if T.unaliased_typ is i8 {
|
||||
encoder.encode_number(workaround_cast[i8](&val))
|
||||
} $else $if T.unaliased_typ is i16 {
|
||||
encoder.encode_number(workaround_cast[i16](&val))
|
||||
} $else $if T.unaliased_typ is int || T.unaliased_typ is i32 {
|
||||
encoder.encode_number(workaround_cast[int](&val))
|
||||
} $else $if T.unaliased_typ is i64 {
|
||||
encoder.encode_number(workaround_cast[i64](&val))
|
||||
} $else $if T.unaliased_typ is usize {
|
||||
encoder.encode_number(workaround_cast[usize](&val))
|
||||
} $else $if T.unaliased_typ is isize {
|
||||
encoder.encode_number(workaround_cast[isize](&val))
|
||||
} $else $if T.unaliased_typ is f32 {
|
||||
encoder.encode_number(workaround_cast[f32](&val))
|
||||
} $else $if T.unaliased_typ is f64 {
|
||||
encoder.encode_number(workaround_cast[f64](&val))
|
||||
} $else $if T.unaliased_typ is $array {
|
||||
encoder.encode_array(val)
|
||||
} $else $if T.unaliased_typ is $map {
|
||||
encoder.encode_map(val)
|
||||
} $else $if T.unaliased_typ is $enum {
|
||||
encoder.encode_enum(val)
|
||||
} $else $if T.unaliased_typ is $sumtype {
|
||||
encoder.encode_sumtype(val)
|
||||
} $else $if T is JsonEncoder { // uses T, because alias could be implementing JsonEncoder, while the base type does not
|
||||
encoder.encode_custom(val)
|
||||
} $else $if T is Encodable { // uses T, because alias could be implementing JsonEncoder, while the base type does not
|
||||
encoder.encode_custom2(val)
|
||||
} $else $if T.unaliased_typ is $struct {
|
||||
unsafe { encoder.encode_struct(val) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_string(val string) {
|
||||
encoder.output << `"`
|
||||
mut buffer_start := 0
|
||||
mut buffer_end := 0
|
||||
for buffer_end < val.len {
|
||||
character := val[buffer_end]
|
||||
match character {
|
||||
`"`, `\\` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << character
|
||||
}
|
||||
`\b` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `b`
|
||||
}
|
||||
`\n` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `n`
|
||||
}
|
||||
`\f` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `f`
|
||||
}
|
||||
`\t` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `t`
|
||||
}
|
||||
`\r` {
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `r`
|
||||
}
|
||||
else {
|
||||
if character < 0x20 { // control characters
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
buffer_end++
|
||||
buffer_start = buffer_end
|
||||
|
||||
encoder.output << `\\`
|
||||
encoder.output << `u`
|
||||
|
||||
hex_string := '${character:04x}'
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 4) }
|
||||
|
||||
continue
|
||||
}
|
||||
if encoder.escape_unicode {
|
||||
if character >= 0b1111_0000 { // four bytes
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
unicode_point_low := val[buffer_end..buffer_end + 4].bytes().byterune() or {
|
||||
0
|
||||
} - 0x10000
|
||||
|
||||
hex_string := '\\u${0xD800 + ((unicode_point_low >> 10) & 0x3FF):04X}\\u${
|
||||
0xDC00 + (unicode_point_low & 0x3FF):04x}'
|
||||
|
||||
buffer_end += 4
|
||||
buffer_start = buffer_end
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 12) }
|
||||
|
||||
continue
|
||||
} else if character >= 0b1110_0000 { // three bytes
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
hex_string := '\\u${val[buffer_end..buffer_end + 3].bytes().byterune() or {
|
||||
0
|
||||
}:04x}'
|
||||
|
||||
buffer_end += 3
|
||||
buffer_start = buffer_end
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 6) }
|
||||
|
||||
continue
|
||||
} else if character >= 0b1100_0000 { // two bytes
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
hex_string := '\\u${val[buffer_end..buffer_end + 2].bytes().byterune() or {
|
||||
0
|
||||
}:04x}'
|
||||
|
||||
buffer_end += 2
|
||||
buffer_start = buffer_end
|
||||
|
||||
unsafe { encoder.output.push_many(hex_string.str, 6) }
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
buffer_end++
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { encoder.output.push_many(val.str + buffer_start, buffer_end - buffer_start) }
|
||||
|
||||
encoder.output << `"`
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_boolean(val bool) {
|
||||
if val {
|
||||
unsafe { encoder.output.push_many(true_string.str, true_string.len) }
|
||||
} else {
|
||||
unsafe { encoder.output.push_many(false_string.str, false_string.len) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_number[T](val T) {
|
||||
integer_val := val.str()
|
||||
$if T is $float {
|
||||
if integer_val[integer_val.len - 1] == `0` { // ends in .0
|
||||
unsafe {
|
||||
integer_val.len -= 2
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_null() {
|
||||
unsafe { encoder.output.push_many(null_string.str, null_string.len) }
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_array[T](val []T) {
|
||||
encoder.output << `[`
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
|
||||
for i, item in val {
|
||||
encoder.encode_value(item)
|
||||
if i < val.len - 1 {
|
||||
encoder.output << `,`
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
} else {
|
||||
if encoder.prettify {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoder.output << `]`
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_map[T](val map[string]T) {
|
||||
encoder.output << `{`
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
|
||||
mut i := 0
|
||||
for key, value in val {
|
||||
encoder.encode_string(key)
|
||||
encoder.output << `:`
|
||||
if encoder.prettify {
|
||||
encoder.output << ` `
|
||||
}
|
||||
encoder.encode_value(value)
|
||||
if i < val.len - 1 {
|
||||
encoder.output << `,`
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
} else {
|
||||
if encoder.prettify {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
encoder.output << `}`
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_enum[T](val T) {
|
||||
if encoder.enum_as_int {
|
||||
encoder.encode_number(workaround_cast[int](&val))
|
||||
} else {
|
||||
mut enum_val := val.str()
|
||||
$if val is $alias {
|
||||
mut i := enum_val.len - 3
|
||||
for enum_val[i] != `(` {
|
||||
i--
|
||||
}
|
||||
|
||||
enum_val = enum_val[i + 1..enum_val.len - 1]
|
||||
}
|
||||
encoder.output << `"`
|
||||
unsafe { encoder.output.push_many(enum_val.str, enum_val.len) }
|
||||
encoder.output << `"`
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_sumtype[T](val T) {
|
||||
$for variant in T.variants {
|
||||
if val is variant {
|
||||
encoder.encode_value(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EncoderFieldInfo {
|
||||
key_name string
|
||||
|
||||
is_skip bool
|
||||
is_omitempty bool
|
||||
}
|
||||
|
||||
@[unsafe]
|
||||
fn (mut encoder Encoder) encode_struct[T](val T) {
|
||||
encoder.output << `{`
|
||||
|
||||
static field_infos := &[]EncoderFieldInfo(nil)
|
||||
|
||||
if field_infos == nil {
|
||||
field_infos = &[]EncoderFieldInfo{}
|
||||
|
||||
$for field in T.fields {
|
||||
mut is_skip := false
|
||||
mut key_name := ''
|
||||
mut is_omitempty := false
|
||||
|
||||
for attr in field.attrs {
|
||||
match attr {
|
||||
'skip' {
|
||||
is_skip = true
|
||||
break
|
||||
}
|
||||
'omitempty' {
|
||||
is_omitempty = true
|
||||
}
|
||||
else {}
|
||||
}
|
||||
|
||||
if attr.starts_with('json:') {
|
||||
if attr == 'json: -' {
|
||||
is_skip = true
|
||||
break
|
||||
}
|
||||
|
||||
key_name = attr[6..]
|
||||
}
|
||||
}
|
||||
field_infos << EncoderFieldInfo{
|
||||
key_name: if key_name == '' { field.name } else { key_name }
|
||||
is_skip: is_skip
|
||||
is_omitempty: is_omitempty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mut i := 0
|
||||
mut is_first := true
|
||||
$for field in T.fields {
|
||||
field_info := field_infos[i]
|
||||
i++
|
||||
|
||||
mut write_field := true
|
||||
|
||||
if field_info.is_skip {
|
||||
write_field = false
|
||||
} else if field_info.is_omitempty {
|
||||
value := val.$(field.name)
|
||||
$if value is $option {
|
||||
if value == none {
|
||||
write_field = false
|
||||
}
|
||||
} $else $if value is string {
|
||||
if value == '' {
|
||||
write_field = false
|
||||
}
|
||||
} $else $if value is $int || value is $float {
|
||||
if value == 0 {
|
||||
write_field = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$if field.indirections != 0 {
|
||||
if val.$(field.name) == unsafe { nil } {
|
||||
write_field = false
|
||||
}
|
||||
}
|
||||
|
||||
if write_field {
|
||||
if is_first {
|
||||
if encoder.prettify {
|
||||
encoder.increment_level()
|
||||
}
|
||||
is_first = false
|
||||
} else {
|
||||
encoder.output << `,`
|
||||
}
|
||||
if encoder.prettify {
|
||||
encoder.add_indent()
|
||||
}
|
||||
|
||||
encoder.encode_string(field_info.key_name)
|
||||
|
||||
encoder.output << `:`
|
||||
if encoder.prettify {
|
||||
encoder.output << ` `
|
||||
}
|
||||
|
||||
$if field is $option {
|
||||
if val.$(field.name) == none {
|
||||
unsafe { encoder.output.push_many(null_string.str, null_string.len) }
|
||||
} else {
|
||||
encoder.encode_value(val.$(field.name))
|
||||
}
|
||||
} $else $if field.indirections == 1 {
|
||||
encoder.encode_value(*val.$(field.name))
|
||||
} $else $if field.indirections == 2 {
|
||||
encoder.encode_value(**val.$(field.name))
|
||||
} $else $if field.indirections == 3 {
|
||||
encoder.encode_value(***val.$(field.name))
|
||||
} $else {
|
||||
encoder.encode_value(val.$(field.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
if encoder.prettify && !is_first {
|
||||
encoder.decrement_level()
|
||||
encoder.add_indent()
|
||||
}
|
||||
|
||||
encoder.output << `}`
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_custom[T](val T) {
|
||||
integer_val := val.to_json()
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) encode_custom2[T](val T) {
|
||||
integer_val := val.json_str()
|
||||
unsafe { encoder.output.push_many(integer_val.str, integer_val.len) }
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) increment_level() {
|
||||
encoder.level++
|
||||
encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level)
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) decrement_level() {
|
||||
encoder.level--
|
||||
encoder.prefix = encoder.newline_string + encoder.indent_string.repeat(encoder.level)
|
||||
}
|
||||
|
||||
fn (mut encoder Encoder) add_indent() {
|
||||
unsafe { encoder.output.push_many(encoder.prefix.str, encoder.prefix.len) }
|
||||
}
|
|
@ -3,427 +3,11 @@
|
|||
// that can be found in the LICENSE file.
|
||||
module json2
|
||||
|
||||
import time
|
||||
import math
|
||||
import strconv
|
||||
|
||||
// Encoder encodes the an `Any` type into JSON representation.
|
||||
// It provides parameters in order to change the end result.
|
||||
pub struct Encoder {
|
||||
pub:
|
||||
newline u8
|
||||
newline_spaces_count int
|
||||
escape_unicode bool = true
|
||||
}
|
||||
|
||||
// byte array versions of the most common tokens/chars to avoid reallocations
|
||||
const null_in_string = 'null'
|
||||
|
||||
const true_in_string = 'true'
|
||||
|
||||
const false_in_string = 'false'
|
||||
|
||||
const empty_array = [u8(`[`), `]`]!
|
||||
|
||||
const comma_rune = `,`
|
||||
|
||||
const colon_rune = `:`
|
||||
|
||||
const quote_rune = `"`
|
||||
|
||||
const back_slash = [u8(`\\`), `\\`]!
|
||||
|
||||
const quote = [u8(`\\`), `"`]!
|
||||
|
||||
const slash = [u8(`\\`), `/`]!
|
||||
|
||||
const null_unicode = [u8(`\\`), `u`, `0`, `0`, `0`, `0`]!
|
||||
|
||||
const ascii_control_characters = ['\\u0000', '\\t', '\\n', '\\r', '\\u0004', '\\u0005', '\\u0006',
|
||||
'\\u0007', '\\b', '\\t', '\\n', '\\u000b', '\\f', '\\r', '\\u000e', '\\u000f', '\\u0010',
|
||||
'\\u0011', '\\u0012', '\\u0013', '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018', '\\u0019',
|
||||
'\\u001a', '\\u001b', '\\u001c', '\\u001d', '\\u001e', '\\u001f']!
|
||||
|
||||
const curly_open_rune = `{`
|
||||
|
||||
const curly_close_rune = `}`
|
||||
|
||||
const ascii_especial_characters = [u8(`\\`), `"`, `/`]!
|
||||
|
||||
// encode is a generic function that encodes a type into a JSON string.
|
||||
@[manualfree]
|
||||
pub fn encode[T](val T) string {
|
||||
$if T is $array {
|
||||
return encode_array(val)
|
||||
} $else {
|
||||
mut count := Count{0}
|
||||
count.count_chars(val)
|
||||
|
||||
mut buf := []u8{cap: count.total}
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
encoder := Encoder{}
|
||||
|
||||
encoder.encode_value(val, mut buf) or {
|
||||
println(err)
|
||||
encoder.encode_value[string]('null', mut buf) or {}
|
||||
}
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
}
|
||||
|
||||
// encode_array is a generic function that encodes a array into a JSON string.
|
||||
@[manualfree]
|
||||
fn encode_array[T](val []T) string {
|
||||
if val.len == 0 {
|
||||
return '[]'
|
||||
}
|
||||
|
||||
mut buf := []u8{}
|
||||
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
|
||||
encoder := Encoder{}
|
||||
encoder.encode_array(val, 1, mut buf) or {
|
||||
println(err)
|
||||
encoder.encode_value[string]('null', mut buf) or {}
|
||||
}
|
||||
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// encode_pretty ...
|
||||
@[deprecated: 'use `encode(..., prettify: true)` instead']
|
||||
@[deprecated_after: '2025-10-30']
|
||||
pub fn encode_pretty[T](typed_data T) string {
|
||||
encoded := encode(typed_data)
|
||||
raw_decoded := decode[Any](encoded) or { 0 }
|
||||
return raw_decoded.prettify_json_str()
|
||||
}
|
||||
|
||||
// encode_value encodes a value to the specific buffer.
|
||||
pub fn (e &Encoder) encode_value[T](val T, mut buf []u8) ! {
|
||||
e.encode_value_with_level[T](val, 1, mut buf)!
|
||||
}
|
||||
|
||||
fn (e &Encoder) encode_newline(level int, mut buf []u8) ! {
|
||||
if e.newline != 0 {
|
||||
buf << e.newline
|
||||
for j := 0; j < level * e.newline_spaces_count; j++ {
|
||||
buf << ` `
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (e &Encoder) encode_map[T](value T, level int, mut buf []u8) ! {
|
||||
buf << curly_open_rune
|
||||
mut idx := 0
|
||||
for k, v in value {
|
||||
e.encode_newline(level, mut buf)!
|
||||
// e.encode_string(k.str(), mut buf)!
|
||||
e.encode_string(k, mut buf)!
|
||||
buf << colon_rune
|
||||
if e.newline != 0 {
|
||||
buf << ` `
|
||||
}
|
||||
|
||||
// workaround to avoid `cannot convert 'struct x__json2__Any' to 'struct string'`
|
||||
$if v is $sumtype {
|
||||
$for variant_value in v.variants {
|
||||
if v is variant_value {
|
||||
e.encode_value_with_level(v, level + 1, mut buf)!
|
||||
}
|
||||
}
|
||||
} $else {
|
||||
e.encode_value_with_level(v, level + 1, mut buf)!
|
||||
}
|
||||
|
||||
if idx < value.len - 1 {
|
||||
buf << comma_rune
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
e.encode_newline(level - 1, mut buf)!
|
||||
buf << curly_close_rune
|
||||
}
|
||||
|
||||
fn (e &Encoder) encode_value_with_level[T](val T, level int, mut buf []u8) ! {
|
||||
$if val is $option {
|
||||
workaround := val
|
||||
if workaround != none {
|
||||
e.encode_value_with_level(val, level, mut buf)!
|
||||
}
|
||||
} $else $if T is string {
|
||||
e.encode_string(val, mut buf)!
|
||||
} $else $if T is $sumtype {
|
||||
$for v in val.variants {
|
||||
if val is v {
|
||||
e.encode_value_with_level(val, level, mut buf)!
|
||||
}
|
||||
}
|
||||
} $else $if T is $alias {
|
||||
// TODO
|
||||
} $else $if T is time.Time {
|
||||
str_value := val.format_rfc3339()
|
||||
buf << quote_rune
|
||||
unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
buf << quote_rune
|
||||
} $else $if T is $map {
|
||||
e.encode_map(val, level, mut buf)!
|
||||
} $else $if T is $array {
|
||||
e.encode_array(val, level, mut buf)!
|
||||
} $else $if T is Encodable {
|
||||
str_value := val.json_str()
|
||||
unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
} $else $if T is Null {
|
||||
unsafe { buf.push_many(null_in_string.str, null_in_string.len) }
|
||||
} $else $if T is $struct {
|
||||
e.encode_struct(val, level, mut buf)!
|
||||
} $else $if T is $enum {
|
||||
str_int := int(val).str()
|
||||
unsafe { buf.push_many(str_int.str, str_int.len) }
|
||||
} $else $if T is $int || T is bool {
|
||||
str_int := val.str()
|
||||
unsafe { buf.push_many(str_int.str, str_int.len) }
|
||||
} $else $if T is $float {
|
||||
str_float := encode_number(val)
|
||||
unsafe { buf.push_many(str_float.str, str_float.len) }
|
||||
} $else {
|
||||
return error('cannot encode value with ${typeof(val).name} type')
|
||||
}
|
||||
}
|
||||
|
||||
fn (e &Encoder) encode_struct[U](val U, level int, mut buf []u8) ! {
|
||||
buf << curly_open_rune
|
||||
mut i := 0
|
||||
mut fields_len := 0
|
||||
|
||||
$for field in U.fields {
|
||||
mut @continue := false
|
||||
for attr in field.attrs {
|
||||
if attr.contains('skip') {
|
||||
@continue = true
|
||||
}
|
||||
if attr.contains('json: ') {
|
||||
if attr.replace('json: ', '') == '-' {
|
||||
@continue = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !@continue {
|
||||
$if field.is_option {
|
||||
if val.$(field.name) != none {
|
||||
fields_len++
|
||||
}
|
||||
} $else {
|
||||
fields_len++
|
||||
}
|
||||
}
|
||||
}
|
||||
$for field in U.fields {
|
||||
mut ignore_field := false
|
||||
|
||||
value := val.$(field.name)
|
||||
mut is_nil := false
|
||||
$if value is $option {
|
||||
if field.indirections > 0 {
|
||||
is_nil = value == none
|
||||
}
|
||||
} $else $if field.indirections > 0 {
|
||||
is_nil = value == unsafe { nil }
|
||||
}
|
||||
mut json_name := ''
|
||||
|
||||
for attr in field.attrs {
|
||||
if attr.contains('skip') {
|
||||
ignore_field = true
|
||||
}
|
||||
if attr.contains('json: ') {
|
||||
json_name = attr.replace('json: ', '')
|
||||
if json_name == '-' {
|
||||
ignore_field = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !ignore_field {
|
||||
$if value is $option {
|
||||
workaround := val.$(field.name)
|
||||
if workaround != none { // smartcast
|
||||
e.encode_newline(level, mut buf)!
|
||||
if json_name != '' {
|
||||
e.encode_string(json_name, mut buf)!
|
||||
} else {
|
||||
e.encode_string(field.name, mut buf)!
|
||||
}
|
||||
buf << colon_rune
|
||||
|
||||
if e.newline != 0 {
|
||||
buf << ` `
|
||||
}
|
||||
e.encode_value_with_level(value, level, mut buf)!
|
||||
} else {
|
||||
ignore_field = true
|
||||
}
|
||||
} $else {
|
||||
is_none := val.$(field.name).str() == 'unknown sum type value' // assert json.encode(StructType[SumTypes]{}) == '{}'
|
||||
if !is_none && !is_nil {
|
||||
e.encode_newline(level, mut buf)!
|
||||
if json_name != '' {
|
||||
e.encode_string(json_name, mut buf)!
|
||||
} else {
|
||||
e.encode_string(field.name, mut buf)!
|
||||
}
|
||||
buf << colon_rune
|
||||
|
||||
if e.newline != 0 {
|
||||
buf << ` `
|
||||
}
|
||||
}
|
||||
|
||||
$if field.indirections != 0 {
|
||||
if val.$(field.name) != unsafe { nil } {
|
||||
$if field.indirections == 1 {
|
||||
e.encode_value_with_level(*val.$(field.name), level + 1, mut
|
||||
buf)!
|
||||
}
|
||||
$if field.indirections == 2 {
|
||||
e.encode_value_with_level(**val.$(field.name), level + 1, mut
|
||||
buf)!
|
||||
}
|
||||
$if field.indirections == 3 {
|
||||
e.encode_value_with_level(***val.$(field.name), level + 1, mut
|
||||
buf)!
|
||||
}
|
||||
}
|
||||
} $else $if field.typ is string {
|
||||
e.encode_string(val.$(field.name).str(), mut buf)!
|
||||
} $else $if field.typ is time.Time {
|
||||
str_value := val.$(field.name).format_rfc3339()
|
||||
buf << quote_rune
|
||||
unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
buf << quote_rune
|
||||
} $else $if field.typ is bool {
|
||||
if value {
|
||||
unsafe { buf.push_many(true_in_string.str, true_in_string.len) }
|
||||
} else {
|
||||
unsafe { buf.push_many(false_in_string.str, false_in_string.len) }
|
||||
}
|
||||
} $else $if field.typ is $int {
|
||||
str_value := val.$(field.name).str()
|
||||
unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
} $else $if field.typ is $float {
|
||||
str_value := encode_number(val.$(field.name))
|
||||
unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
} $else $if field.is_array {
|
||||
// TODO: replace for `field.typ is $array`
|
||||
e.encode_array(value, level + 1, mut buf)!
|
||||
} $else $if field.typ is $array {
|
||||
// e.encode_array(value, level + 1, mut buf)! // FIXME: error: could not infer generic type `U` in call to `encode_array`
|
||||
} $else $if field.typ is $struct {
|
||||
e.encode_struct(value, level + 1, mut buf)!
|
||||
} $else $if field.is_map {
|
||||
e.encode_map(value, level + 1, mut buf)!
|
||||
} $else $if field.is_enum {
|
||||
// TODO: replace for `field.typ is $enum`
|
||||
// str_value := int(val.$(field.name)).str()
|
||||
// unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
e.encode_value_with_level(val.$(field.name), level + 1, mut buf)!
|
||||
} $else $if field.typ is $enum {
|
||||
} $else $if field.typ is $sumtype {
|
||||
field_value := val.$(field.name)
|
||||
if field_value.str() != 'unknown sum type value' {
|
||||
$for v in field_value.variants {
|
||||
if field_value is v {
|
||||
e.encode_value_with_level(field_value, level, mut buf)!
|
||||
}
|
||||
}
|
||||
}
|
||||
} $else $if field.typ is $alias {
|
||||
$if field.unaliased_typ is string {
|
||||
e.encode_string(val.$(field.name).str(), mut buf)!
|
||||
} $else $if field.unaliased_typ is time.Time {
|
||||
parsed_time := time.parse(val.$(field.name).str()) or { time.Time{} }
|
||||
e.encode_string(parsed_time.format_rfc3339(), mut buf)!
|
||||
} $else $if field.unaliased_typ is bool {
|
||||
if val.$(field.name) {
|
||||
unsafe { buf.push_many(true_in_string.str, true_in_string.len) }
|
||||
} else {
|
||||
unsafe { buf.push_many(false_in_string.str, false_in_string.len) }
|
||||
}
|
||||
} $else $if field.unaliased_typ is $int {
|
||||
str_value := val.$(field.name).str()
|
||||
unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
} $else $if field.unaliased_typ is $float {
|
||||
str_value := encode_number(val)
|
||||
unsafe { buf.push_many(str_value.str, str_value.len) }
|
||||
} $else $if field.unaliased_typ is $array {
|
||||
// TODO
|
||||
} $else $if field.unaliased_typ is $struct {
|
||||
e.encode_struct(value, level + 1, mut buf)!
|
||||
} $else $if field.unaliased_typ is $enum {
|
||||
// TODO
|
||||
} $else $if field.unaliased_typ is $sumtype {
|
||||
// TODO
|
||||
} $else {
|
||||
return error('the alias ${typeof(val).name} cannot be encoded')
|
||||
}
|
||||
} $else {
|
||||
return error('type ${typeof(val).name} cannot be array encoded')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i < fields_len - 1 && !ignore_field {
|
||||
if !is_nil {
|
||||
buf << comma_rune
|
||||
}
|
||||
}
|
||||
if !ignore_field {
|
||||
i++
|
||||
}
|
||||
}
|
||||
e.encode_newline(level - 1, mut buf)!
|
||||
buf << curly_close_rune
|
||||
// b.measure('encode_struct')
|
||||
}
|
||||
|
||||
fn (e &Encoder) encode_array[U](val []U, level int, mut buf []u8) ! {
|
||||
if val.len == 0 {
|
||||
unsafe { buf.push_many(&empty_array[0], empty_array.len) }
|
||||
return
|
||||
}
|
||||
buf << `[`
|
||||
for i in 0 .. val.len {
|
||||
e.encode_newline(level, mut buf)!
|
||||
|
||||
$if U is string || U is bool || U is $int || U is $float {
|
||||
e.encode_value_with_level(val[i], level + 1, mut buf)!
|
||||
} $else $if U is $array {
|
||||
e.encode_array(val[i], level + 1, mut buf)!
|
||||
} $else $if U is $struct {
|
||||
e.encode_struct(val[i], level + 1, mut buf)!
|
||||
} $else $if U is $sumtype {
|
||||
e.encode_value_with_level(val[i], level + 1, mut buf)!
|
||||
} $else $if U is $enum {
|
||||
// TODO: test
|
||||
e.encode_value_with_level(val[i], level + 1, mut buf)!
|
||||
} $else {
|
||||
return error('type ${typeof(val).name} cannot be array encoded')
|
||||
}
|
||||
if i < val.len - 1 {
|
||||
buf << comma_rune
|
||||
}
|
||||
}
|
||||
|
||||
e.encode_newline(level - 1, mut buf)!
|
||||
buf << `]`
|
||||
return encode(typed_data, prettify: true)
|
||||
}
|
||||
|
||||
// str returns the JSON string representation of the `map[string]Any` type.
|
||||
|
@ -452,161 +36,8 @@ pub fn (f Any) json_str() string {
|
|||
}
|
||||
|
||||
// prettify_json_str returns the pretty-formatted JSON string representation of the `Any` type.
|
||||
@[manualfree]
|
||||
@[deprecated: 'use `encode(Any(...), prettify: true)` instead']
|
||||
@[deprecated_after: '2025-10-30']
|
||||
pub fn (f Any) prettify_json_str() string {
|
||||
mut buf := []u8{}
|
||||
defer {
|
||||
unsafe { buf.free() }
|
||||
}
|
||||
mut enc := Encoder{
|
||||
newline: `\n`
|
||||
newline_spaces_count: 2
|
||||
}
|
||||
enc.encode_value(f, mut buf) or {}
|
||||
return buf.bytestr()
|
||||
}
|
||||
|
||||
// TODO: Need refactor. Is so slow. The longer the string, the lower the performance.
|
||||
// encode_string returns the JSON spec-compliant version of the string.
|
||||
@[direct_array_access]
|
||||
fn (e &Encoder) encode_string(s string, mut buf []u8) ! {
|
||||
if s == '' {
|
||||
empty := [u8(quote_rune), quote_rune]!
|
||||
unsafe { buf.push_many(&empty[0], 2) }
|
||||
return
|
||||
}
|
||||
mut last_no_buffer_expansible_char_position_candidate := 0
|
||||
buf << quote_rune
|
||||
|
||||
if !e.escape_unicode {
|
||||
unsafe {
|
||||
buf.push_many(s.str, s.len)
|
||||
buf << quote_rune
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for idx := 0; idx < s.len; idx++ {
|
||||
current_byte := s[idx]
|
||||
|
||||
mut current_utf8_len := ((0xe5000000 >> ((current_byte >> 3) & 0x1e)) & 3) + 1
|
||||
|
||||
current_value_cause_buffer_expansion :=
|
||||
(current_utf8_len == 1 && ((current_byte < 32 || current_byte > 127)
|
||||
|| current_byte in ascii_especial_characters)) || current_utf8_len == 3
|
||||
|
||||
if !current_value_cause_buffer_expansion {
|
||||
// while it is not the last one
|
||||
if idx < s.len - 1 {
|
||||
if s.len > (idx + current_utf8_len) {
|
||||
if current_utf8_len == 2 || current_utf8_len == 4 {
|
||||
// runes like: ã, ü, etc.
|
||||
// Emojis ranges
|
||||
// (0x1F300, 0x1F5FF), # Miscellaneous Symbols and Pictographs
|
||||
// (0x1F600, 0x1F64F), # Emoticons
|
||||
// (0x1F680, 0x1F6FF), # Transport and Map Symbols
|
||||
idx += current_utf8_len - 1
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
buf.push_many(s.str + last_no_buffer_expansible_char_position_candidate,
|
||||
s.len - last_no_buffer_expansible_char_position_candidate)
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if idx == s.len - 1 {
|
||||
unsafe {
|
||||
buf.push_many(s.str + last_no_buffer_expansible_char_position_candidate,
|
||||
s.len - last_no_buffer_expansible_char_position_candidate)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if idx > 0 {
|
||||
length := idx - last_no_buffer_expansible_char_position_candidate
|
||||
unsafe {
|
||||
buf.push_many(s.str + last_no_buffer_expansible_char_position_candidate,
|
||||
length)
|
||||
}
|
||||
last_no_buffer_expansible_char_position_candidate = idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
if current_utf8_len == 1 {
|
||||
if current_byte < 32 {
|
||||
// ASCII Control Characters
|
||||
unsafe {
|
||||
buf.push_many(ascii_control_characters[current_byte].str, ascii_control_characters[current_byte].len)
|
||||
}
|
||||
last_no_buffer_expansible_char_position_candidate = idx + 1
|
||||
} else if current_byte >= 32 && current_byte < 128 {
|
||||
// ASCII especial characters
|
||||
if current_byte == `\\` {
|
||||
unsafe { buf.push_many(&back_slash[0], back_slash.len) }
|
||||
last_no_buffer_expansible_char_position_candidate = idx + 1
|
||||
continue
|
||||
} else if current_byte == `"` {
|
||||
unsafe { buf.push_many("e[0], quote.len) }
|
||||
last_no_buffer_expansible_char_position_candidate = idx + 1
|
||||
continue
|
||||
} else if current_byte == `/` {
|
||||
unsafe { buf.push_many(&slash[0], slash.len) }
|
||||
last_no_buffer_expansible_char_position_candidate = idx + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else if current_utf8_len == 3 {
|
||||
// runes like: ✔, ひらがな ...
|
||||
|
||||
// Handle multi-byte characters byte-by-byte
|
||||
mut codepoint := u32(current_byte & ((1 << (7 - current_utf8_len)) - 1))
|
||||
for j in 1 .. current_utf8_len {
|
||||
if idx + j >= s.len {
|
||||
// Incomplete UTF-8 sequence, TODO handle error
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
|
||||
mut b := s[idx + j]
|
||||
if (b & 0xC0) != 0x80 {
|
||||
// Invalid continuation byte, TODO handle error
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
|
||||
codepoint = u32((codepoint << 6) | (b & 0x3F))
|
||||
}
|
||||
// runes like: ✔, ひらがな ...
|
||||
unsafe { buf.push_many(&null_unicode[0], null_unicode.len) }
|
||||
buf[buf.len - 1] = hex_digit(codepoint & 0xF)
|
||||
buf[buf.len - 2] = hex_digit((codepoint >> 4) & 0xF)
|
||||
buf[buf.len - 3] = hex_digit((codepoint >> 8) & 0xF)
|
||||
buf[buf.len - 4] = hex_digit((codepoint >> 12) & 0xF)
|
||||
idx += current_utf8_len - 1
|
||||
last_no_buffer_expansible_char_position_candidate = idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
buf << quote_rune
|
||||
}
|
||||
|
||||
fn hex_digit(n u32) u8 {
|
||||
if n < 10 {
|
||||
return `0` + n
|
||||
}
|
||||
return `a` + (n - 10)
|
||||
}
|
||||
|
||||
fn encode_number(value f64) string {
|
||||
if math.is_nan(value) || math.is_inf(value, 0) {
|
||||
return 'null'
|
||||
} else if value == f64(int(value)) {
|
||||
return int(value).str()
|
||||
} else {
|
||||
// TODO:cjson Try 15 decimal places of precision to avoid nonsignificant nonzero digits
|
||||
// If not, print with 17 decimal places of precision
|
||||
// strconv.f64_to_str_l try max 18 digits instead.
|
||||
return strconv.f64_to_str_l(value)
|
||||
}
|
||||
return encode(f, prettify: true)
|
||||
}
|
||||
|
|
|
@ -337,11 +337,11 @@ fn test_str() {
|
|||
assert sample_data['i32'] or { 0 }.str() == '7'
|
||||
assert sample_data['int'] or { 0 }.str() == '8'
|
||||
assert sample_data['i64'] or { 0 }.str() == '9'
|
||||
assert sample_data['f32'] or { 0 }.str() == '2.299999952316284'
|
||||
assert sample_data['f32'] or { 0 }.str() == '2.3'
|
||||
assert sample_data['f64'] or { 0 }.str() == '1.283'
|
||||
assert sample_data['bool'] or { 0 }.str() == 'false'
|
||||
assert sample_data['str'] or { 0 }.str() == 'test'
|
||||
assert sample_data['null'] or { 0 }.str() == 'null'
|
||||
assert sample_data['arr'] or { 'not lol' }.str() == '["lol"]'
|
||||
assert sample_data.str() == '{"u8":1,"u16":2,"u32":3,"u64":4,"i8":5,"i16":6,"i32":7,"int":8,"i64":9,"f32":2.299999952316284,"f64":1.283,"bool":false,"str":"test","null":null,"arr":["lol"],"obj":{"foo":10}}'
|
||||
assert sample_data.str() == '{"u8":1,"u16":2,"u32":3,"u64":4,"i8":5,"i16":6,"i32":7,"int":8,"i64":9,"f32":2.3,"f64":1.283,"bool":false,"str":"test","null":null,"arr":["lol"],"obj":{"foo":10}}'
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ fn test_values() {
|
|||
// assert json.decode[OptAnyArrStruct]('{"val":[5,null,10]}')!.val == [?json.Any(5),json.Null{},10] skipped because test still fails even though they're the same
|
||||
|
||||
assert json.encode[AnyStruct[json.Any]](AnyStruct[json.Any]{json.Any(5)}) == '{"val":5}'
|
||||
assert json.encode[OptAnyStruct[json.Any]](OptAnyStruct[json.Any]{none}) == '{}'
|
||||
assert json.encode[OptAnyStruct[json.Any]](OptAnyStruct[json.Any]{none}) == '{"val":null}'
|
||||
assert json.encode[AnyStruct[[]json.Any]](AnyStruct[[]json.Any]{[json.Any(5), 10]}) == '{"val":[5,10]}'
|
||||
// assert json.encode[OptAnyArrStruct](OptAnyArrStruct{[?json.Any(5),none,10]}) == '{"val":[5,null,10]}' encode_array has not implemented optional arrays yet
|
||||
}
|
||||
|
|
|
@ -20,5 +20,5 @@ fn test_main() {
|
|||
fn test_none() {
|
||||
disk := Disk{}
|
||||
disk_str := json2.encode[Disk](disk)
|
||||
assert disk_str == '{"dev":""}'
|
||||
assert disk_str == '{"dev":"","size":null}'
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@ pub mut:
|
|||
|
||||
fn test_main() {
|
||||
res := json2.encode(JoseHeader{ alg: 'HS256' })
|
||||
assert res == '{"alg":"HS256","typ":"JWT"}'
|
||||
assert res == '{"cty":null,"alg":"HS256","typ":"JWT"}'
|
||||
}
|
||||
|
|
|
@ -65,30 +65,38 @@ fn test_types() {
|
|||
}
|
||||
}) == '{"val":{"val":1}}'
|
||||
|
||||
assert json.encode(StructType[Enumerates]{}) == '{"val":0}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.a }) == '{"val":0}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.d }) == '{"val":3}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.e }) == '{"val":99}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.f }) == '{"val":100}'
|
||||
assert json.encode(StructType[Enumerates]{}, enum_as_int: true) == '{"val":0}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.a },
|
||||
enum_as_int: true
|
||||
) == '{"val":0}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.d },
|
||||
enum_as_int: true
|
||||
) == '{"val":3}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.e },
|
||||
enum_as_int: true
|
||||
) == '{"val":99}'
|
||||
assert json.encode(StructType[Enumerates]{ val: Enumerates.f },
|
||||
enum_as_int: true
|
||||
) == '{"val":100}'
|
||||
}
|
||||
|
||||
fn test_option_types() {
|
||||
assert json.encode(StructTypeOption[string]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[string]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[string]{ val: none }) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[string]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[string]{ val: '' }) == '{"val":""}'
|
||||
assert json.encode(StructTypeOption[string]{ val: 'a' }) == '{"val":"a"}'
|
||||
|
||||
assert json.encode(StructTypeOption[bool]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[bool]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[bool]{ val: none }) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[bool]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[bool]{ val: false }) == '{"val":false}'
|
||||
assert json.encode(StructTypeOption[bool]{ val: true }) == '{"val":true}'
|
||||
|
||||
assert json.encode(StructTypeOption[int]{ val: none }) == '{}'
|
||||
assert json.encode(StructTypeOption[int]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[int]{ val: none }) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[int]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[int]{ val: 0 }) == '{"val":0}'
|
||||
assert json.encode(StructTypeOption[int]{ val: 1 }) == '{"val":1}'
|
||||
|
||||
assert json.encode(StructTypeOption[time.Time]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[time.Time]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[time.Time]{ val: time.Time{} }) == '{"val":"0000-00-00T00:00:00.000Z"}'
|
||||
assert json.encode(StructTypeOption[time.Time]{ val: fixed_time }) == '{"val":"2022-03-11T13:54:25.000Z"}'
|
||||
|
||||
|
@ -98,7 +106,7 @@ fn test_option_types() {
|
|||
}
|
||||
}) == '{"val":{"val":1}}'
|
||||
|
||||
assert json.encode(StructTypeOption[Enumerates]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[Enumerates]{}) == '{"val":null}'
|
||||
}
|
||||
|
||||
fn test_array() {
|
||||
|
@ -157,42 +165,42 @@ fn test_array() {
|
|||
}
|
||||
|
||||
fn test_option_array() {
|
||||
assert json.encode(StructTypeOption[[]string]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]string]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[[]string]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]string]{ val: ['0'] }) == '{"val":["0"]}'
|
||||
assert json.encode(StructTypeOption[[]string]{ val: ['1'] }) == '{"val":["1"]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]int]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]int]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [0] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [1] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]int]{ val: [0, 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]u8]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]u8]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [u8(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [u8(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]u8]{ val: [u8(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]i64]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]i64]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [i64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [i64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]i64]{ val: [i64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]u64]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]u64]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [u64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [u64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]u64]{ val: [u64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]f64]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]f64]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [f64(0)] }) == '{"val":[0]}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [f64(1)] }) == '{"val":[1]}'
|
||||
assert json.encode(StructTypeOption[[]f64]{ val: [f64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}'
|
||||
|
||||
assert json.encode(StructTypeOption[[]bool]{}) == '{}'
|
||||
assert json.encode(StructTypeOption[[]bool]{}) == '{"val":null}'
|
||||
assert json.encode(StructTypeOption[[]bool]{ val: [] }) == '{"val":[]}'
|
||||
assert json.encode(StructTypeOption[[]bool]{ val: [true] }) == '{"val":[true]}'
|
||||
assert json.encode(StructTypeOption[[]bool]{ val: [false] }) == '{"val":[false]}'
|
||||
|
|
|
@ -8,19 +8,19 @@ mut:
|
|||
}
|
||||
|
||||
fn test_json_string_characters() {
|
||||
assert json.encode([u8(`/`)].bytestr()).bytes() == r'"\/"'.bytes()
|
||||
assert json.encode([u8(`/`)].bytestr()).bytes() == r'"/"'.bytes()
|
||||
assert json.encode([u8(`\\`)].bytestr()).bytes() == r'"\\"'.bytes()
|
||||
assert json.encode([u8(`"`)].bytestr()).bytes() == r'"\""'.bytes()
|
||||
assert json.encode([u8(`\n`)].bytestr()).bytes() == r'"\n"'.bytes()
|
||||
assert json.encode(r'\n\r') == r'"\\n\\r"'
|
||||
assert json.encode('\\n') == r'"\\n"'
|
||||
assert json.encode(r'\n\r\b') == r'"\\n\\r\\b"'
|
||||
assert json.encode(r'\"/').bytes() == r'"\\\"\/"'.bytes()
|
||||
assert json.encode(r'\"/').bytes() == r'"\\\"/"'.bytes()
|
||||
|
||||
assert json.encode(r'\n\r\b\f\t\\\"\/') == r'"\\n\\r\\b\\f\\t\\\\\\\"\\\/"'
|
||||
assert json.encode(r'\n\r\b\f\t\\\"\/') == r'"\\n\\r\\b\\f\\t\\\\\\\"\\/"'
|
||||
|
||||
text := json.raw_decode(r'"\n\r\b\f\t\\\"\/"') or { '' }
|
||||
assert text.json_str() == '"\\n\\r\\b\\f\\t\\\\\\"\\/"'
|
||||
assert text.json_str() == '"\\n\\r\\b\\f\\t\\\\\\"/"'
|
||||
|
||||
assert json.encode("fn main(){nprintln('Hello World! Helo \$a')\n}") == '"fn main(){nprintln(\'Hello World! Helo \$a\')\\n}"'
|
||||
assert json.encode(' And when "\'s are in the string, along with # "') == '" And when \\"\'s are in the string, along with # \\""'
|
||||
|
@ -42,8 +42,8 @@ fn test_json_escape_low_chars() {
|
|||
fn test_json_string() {
|
||||
text := json.Any('te✔st')
|
||||
|
||||
assert text.json_str() == r'"te\u2714st"'
|
||||
assert json.encode('te✔st') == r'"te\u2714st"'
|
||||
assert text.json_str() == r'"te✔st"'
|
||||
assert json.encode('te✔st', escape_unicode: true) == r'"te\u2714st"'
|
||||
|
||||
boolean := json.Any(true)
|
||||
assert boolean.json_str() == 'true'
|
||||
|
@ -67,9 +67,9 @@ fn test_json_string_emoji() {
|
|||
|
||||
fn test_json_string_non_ascii() {
|
||||
text := json.Any('ひらがな')
|
||||
assert text.json_str() == r'"\u3072\u3089\u304c\u306a"'
|
||||
assert text.json_str() == r'"ひらがな"'
|
||||
|
||||
assert json.encode('ひらがな') == r'"\u3072\u3089\u304c\u306a"'
|
||||
assert json.encode('ひらがな', escape_unicode: true) == r'"\u3072\u3089\u304c\u306a"'
|
||||
}
|
||||
|
||||
fn test_utf8_strings_are_not_modified() {
|
||||
|
@ -83,30 +83,13 @@ fn test_utf8_strings_are_not_modified() {
|
|||
|
||||
fn test_encoder_unescaped_utf32() ! {
|
||||
jap_text := json.Any('ひらがな')
|
||||
enc := json.Encoder{
|
||||
escape_unicode: false
|
||||
}
|
||||
|
||||
mut sb := strings.new_builder(20)
|
||||
defer {
|
||||
unsafe { sb.free() }
|
||||
}
|
||||
|
||||
enc.encode_value(jap_text, mut sb)!
|
||||
|
||||
assert sb.str() == '"${jap_text}"'
|
||||
sb.go_back_to(0)
|
||||
assert json.encode(jap_text) == '"${jap_text}"'
|
||||
|
||||
emoji_text := json.Any('🐈')
|
||||
enc.encode_value(emoji_text, mut sb)!
|
||||
assert sb.str() == '"${emoji_text}"'
|
||||
assert json.encode(emoji_text) == '"${emoji_text}"'
|
||||
|
||||
mut buf := []u8{cap: 14}
|
||||
|
||||
enc.encode_value('ひらがな', mut buf)!
|
||||
|
||||
assert buf.len == 14
|
||||
assert buf.bytestr() == '"ひらがな"'
|
||||
assert json.encode('ひらがな') == '"ひらがな"'
|
||||
}
|
||||
|
||||
fn test_encoder_prettify() {
|
||||
|
@ -117,16 +100,7 @@ fn test_encoder_prettify() {
|
|||
'map': json.Any('map inside a map')
|
||||
}
|
||||
}
|
||||
enc := json.Encoder{
|
||||
newline: `\n`
|
||||
newline_spaces_count: 2
|
||||
}
|
||||
mut sb := strings.new_builder(20)
|
||||
defer {
|
||||
unsafe { sb.free() }
|
||||
}
|
||||
enc.encode_value(obj, mut sb)!
|
||||
assert sb.str() == '{
|
||||
assert json.encode(obj, prettify: true, indent_string: ' ') == '{
|
||||
"hello": "world",
|
||||
"arr": [
|
||||
"im a string",
|
||||
|
@ -180,35 +154,13 @@ fn test_encode_simple() {
|
|||
}
|
||||
|
||||
fn test_encode_value() {
|
||||
json_enc := json.Encoder{
|
||||
newline: `\n`
|
||||
newline_spaces_count: 2
|
||||
escape_unicode: false
|
||||
}
|
||||
|
||||
mut manifest := map[string]json.Any{}
|
||||
|
||||
manifest['server_path'] = json.Any('new_path')
|
||||
manifest['last_updated'] = json.Any('timestamp.format_ss()')
|
||||
manifest['from_source'] = json.Any('from_source')
|
||||
|
||||
mut sb := strings.new_builder(64)
|
||||
mut buffer := []u8{}
|
||||
json_enc.encode_value(manifest, mut buffer)!
|
||||
|
||||
assert buffer.len > 0
|
||||
assert buffer == [u8(123), 10, 32, 32, 34, 115, 101, 114, 118, 101, 114, 95, 112, 97, 116,
|
||||
104, 34, 58, 32, 34, 110, 101, 119, 95, 112, 97, 116, 104, 34, 44, 10, 32, 32, 34, 108,
|
||||
97, 115, 116, 95, 117, 112, 100, 97, 116, 101, 100, 34, 58, 32, 34, 116, 105, 109, 101,
|
||||
115, 116, 97, 109, 112, 46, 102, 111, 114, 109, 97, 116, 95, 115, 115, 40, 41, 34, 44,
|
||||
10, 32, 32, 34, 102, 114, 111, 109, 95, 115, 111, 117, 114, 99, 101, 34, 58, 32, 34, 102,
|
||||
114, 111, 109, 95, 115, 111, 117, 114, 99, 101, 34, 10, 125]
|
||||
|
||||
sb.write(buffer)!
|
||||
|
||||
unsafe { buffer.free() }
|
||||
|
||||
assert sb.str() == r'{
|
||||
assert json.encode(manifest, prettify: true, indent_string: ' ') == r'{
|
||||
"server_path": "new_path",
|
||||
"last_updated": "timestamp.format_ss()",
|
||||
"from_source": "from_source"
|
||||
|
|
|
@ -29,7 +29,7 @@ fn test_simple() {
|
|||
name: 'João'
|
||||
}
|
||||
x := Employee{'Peter', 28, 95000.5, .worker, sub_employee}
|
||||
s := json.encode[Employee](x)
|
||||
s := json.encode[Employee](x, enum_as_int: true)
|
||||
assert s == '{"name":"Peter","age":28,"salary":95000.5,"title":2,"sub_employee":{"name":"João","age":0,"salary":0,"title":0}}'
|
||||
|
||||
y := json.decode[Employee](s) or {
|
||||
|
@ -232,7 +232,7 @@ struct Foo2 {
|
|||
|
||||
fn test_pretty() {
|
||||
foo := Foo2{1, 2, 3, 4, -1, -2, -3, -4, true, 'abc', 'aliens'}
|
||||
assert json.encode_pretty(foo) == '{
|
||||
assert json.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"ux8": 1,
|
||||
"ux16": 2,
|
||||
"ux32": 3,
|
||||
|
@ -387,7 +387,7 @@ fn test_encode_decode_sumtype() {
|
|||
]
|
||||
}
|
||||
|
||||
enc := json.encode(game)
|
||||
enc := json.encode(game, enum_as_int: true)
|
||||
|
||||
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke"},"other":[{"tag":"Pen"},{"tag":"Cookie"},1,"Stool","${t.format_rfc3339()}"]}'
|
||||
}
|
||||
|
@ -414,8 +414,9 @@ fn test_option_instead_of_omit_empty() {
|
|||
foo := Foo31{
|
||||
name: 'Bob'
|
||||
}
|
||||
assert json.encode_pretty(foo) == '{
|
||||
"name": "Bob"
|
||||
assert json.encode(foo, prettify: true, indent_string: ' ') == '{
|
||||
"name": "Bob",
|
||||
"age": null
|
||||
}'
|
||||
}
|
||||
|
||||
|
|
|
@ -23,11 +23,6 @@ pub type Any = []Any
|
|||
| u8
|
||||
| Null
|
||||
|
||||
// Encodable is an interface, that allows custom implementations for encoding structs to their string based JSON representations.
|
||||
pub interface Encodable {
|
||||
json_str() string
|
||||
}
|
||||
|
||||
// Null is a simple representation of the `null` value in JSON.
|
||||
pub struct Null {
|
||||
is_null bool = true
|
||||
|
@ -39,6 +34,11 @@ pub const null = Null{}
|
|||
// from_json_null implements a custom decoder for json2
|
||||
pub fn (mut n Null) from_json_null() {}
|
||||
|
||||
// to_json implements a custom encoder for json2
|
||||
pub fn (n Null) to_json() string {
|
||||
return 'null'
|
||||
}
|
||||
|
||||
// ValueKind enumerates the kinds of possible values of the Any sumtype.
|
||||
enum ValueKind {
|
||||
unknown
|
||||
|
@ -49,16 +49,3 @@ enum ValueKind {
|
|||
boolean
|
||||
null
|
||||
}
|
||||
|
||||
// str returns the string representation of the specific ValueKind.
|
||||
fn (k ValueKind) str() string {
|
||||
return match k {
|
||||
.unknown { 'unknown' }
|
||||
.array { 'array' }
|
||||
.object { 'object' }
|
||||
.string { 'string' }
|
||||
.number { 'number' }
|
||||
.boolean { 'boolean' }
|
||||
.null { 'null' }
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue