json2: cleanup (#20347)

This commit is contained in:
Hitalo Souza 2024-01-12 16:48:28 -04:00 committed by GitHub
parent bcd77eddce
commit 62872c677f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 161 additions and 214 deletions

View File

@ -33,6 +33,7 @@ pub fn (t Time) format_ss_nano() string {
// format_rfc3339 returns a date string in "YYYY-MM-DDTHH:mm:ss.123Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html) // format_rfc3339 returns a date string in "YYYY-MM-DDTHH:mm:ss.123Z" format (24 hours, see https://www.rfc-editor.org/rfc/rfc3339.html)
// RFC3339 is an Internet profile, based on the ISO 8601 standard for for representation of dates and times using the Gregorian calendar. // RFC3339 is an Internet profile, based on the ISO 8601 standard for for representation of dates and times using the Gregorian calendar.
// It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols. // It is intended to improve consistency and interoperability, when representing and using date and time in Internet protocols.
@[markused]
pub fn (t Time) format_rfc3339() string { pub fn (t Time) format_rfc3339() string {
u := t.local_to_utc() u := t.local_to_utc()
return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond / 1_000_000):03d}Z' return '${u.year:04d}-${u.month:02d}-${u.day:02d}T${u.hour:02d}:${u.minute:02d}:${u.second:02d}.${(u.nanosecond / 1_000_000):03d}Z'

View File

@ -123,6 +123,6 @@ fn test_str() {
assert sample_data['bool'] or { 0 }.str() == 'false' assert sample_data['bool'] or { 0 }.str() == 'false'
assert sample_data['str'] or { 0 }.str() == 'test' assert sample_data['str'] or { 0 }.str() == 'test'
assert sample_data['null'] or { 0 }.str() == 'null' assert sample_data['null'] or { 0 }.str() == 'null'
assert sample_data['arr'] or { 0 }.str() == '["lol"]' assert sample_data['arr'] or { 'not lol' }.str() == '["lol"]'
assert sample_data.str() == '{"int":1,"i64":128,"f32":2.0,"f64":1.283,"bool":false,"str":"test","null":null,"arr":["lol"],"obj":{"foo":10}}' assert sample_data.str() == '{"int":1,"i64":128,"f32":2.0,"f64":1.283,"bool":false,"str":"test","null":null,"arr":["lol"],"obj":{"foo":10}}'
} }

View File

@ -185,7 +185,9 @@ fn (mut p Parser) decode_value() !Any {
} }
} }
} }
return Any(null) return InvalidTokenError{
token: p.tok
}
} }
@[manualfree] @[manualfree]

View File

@ -253,7 +253,7 @@ fn test_pointer() {
} }
fn test_sumtypes() { fn test_sumtypes() {
assert json.encode(StructType[SumTypes]{}) == '{}' assert json.encode(StructType[SumTypes]{}) == '{}' // is_none := val.$(field.name).str() == 'unknown sum type value'
assert json.encode(StructType[SumTypes]{ val: '' }) == '{"val":""}' assert json.encode(StructType[SumTypes]{ val: '' }) == '{"val":""}'
assert json.encode(StructType[SumTypes]{ val: 'a' }) == '{"val":"a"}' assert json.encode(StructType[SumTypes]{ val: 'a' }) == '{"val":"a"}'

View File

@ -50,80 +50,6 @@ fn (e &Encoder) encode_newline(level int, mut buf []u8) ! {
} }
} }
fn (e &Encoder) encode_any(val Any, level int, mut buf []u8) ! {
match val {
string {
e.encode_string(val, mut buf)!
}
bool {
if val == true {
unsafe { buf.push_many(json2.true_in_string.str, json2.true_in_string.len) }
} else {
unsafe { buf.push_many(json2.false_in_string.str, json2.false_in_string.len) }
}
}
i8, i16, int, i64 {
str_int := val.str()
unsafe { buf.push_many(str_int.str, str_int.len) }
}
u8, u16, u32, u64 {
str_int := val.str()
unsafe { buf.push_many(str_int.str, str_int.len) }
}
f32, f64 {
$if !nofloat ? {
str_float := val.str()
unsafe { buf.push_many(str_float.str, str_float.len) }
if str_float[str_float.len - 1] == `.` {
buf << json2.zero_rune
}
return
}
buf << json2.zero_rune
}
map[string]Any {
buf << json2.curly_open_rune
mut i := 0
for k, v in val {
e.encode_newline(level, mut buf)!
e.encode_string(k, mut buf)!
buf << json2.colon_rune
if e.newline != 0 {
buf << ` `
}
e.encode_value_with_level(v, level + 1, mut buf)!
if i < val.len - 1 {
buf << json2.comma_rune
}
i++
}
e.encode_newline(level - 1, mut buf)!
buf << json2.curly_close_rune
}
[]Any {
buf << `[`
for i in 0 .. val.len {
e.encode_newline(level, mut buf)!
e.encode_value_with_level(val[i], level + 1, mut buf)!
if i < val.len - 1 {
buf << json2.comma_rune
}
}
e.encode_newline(level - 1, mut buf)!
buf << `]`
}
time.Time {
str_time := val.format_rfc3339()
buf << `"`
unsafe { buf.push_many(str_time.str, str_time.len) }
buf << `"`
}
Null {
unsafe { buf.push_many(json2.null_in_bytes.str, json2.null_in_bytes.len) }
}
}
}
fn (e &Encoder) encode_map[T](value T, level int, mut buf []u8) ! { fn (e &Encoder) encode_map[T](value T, level int, mut buf []u8) ! {
buf << json2.curly_open_rune buf << json2.curly_open_rune
mut idx := 0 mut idx := 0
@ -135,7 +61,18 @@ fn (e &Encoder) encode_map[T](value T, level int, mut buf []u8) ! {
if e.newline != 0 { if e.newline != 0 {
buf << ` ` buf << ` `
} }
e.encode_value_with_level(v, level + 1, mut 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 { if idx < value.len - 1 {
buf << json2.comma_rune buf << json2.comma_rune
} }
@ -147,31 +84,35 @@ fn (e &Encoder) encode_map[T](value T, level int, mut buf []u8) ! {
} }
fn (e &Encoder) encode_value_with_level[T](val T, level int, mut buf []u8) ! { fn (e &Encoder) encode_value_with_level[T](val T, level int, mut buf []u8) ! {
$if T is string { $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)! e.encode_string(val, mut buf)!
} $else $if T is Any {
e.encode_any(val, level, mut buf)!
} $else $if T is $sumtype { } $else $if T is $sumtype {
if val.str() != 'unknown sum type value' { $for v in val.variants {
$for v in val.variants { if val is v {
if val is v { e.encode_value_with_level(val, level, mut buf)!
e.encode_value_with_level(val, level, mut buf)!
}
} }
} }
} $else $if T is $alias { } $else $if T is $alias {
// TODO // TODO
} $else $if T is time.Time { } $else $if T is time.Time {
parsed_time := time.parse(val.str()) or { time.Time{} } str_value := val.format_rfc3339()
e.encode_string(parsed_time.format_rfc3339(), mut buf)! buf << json2.quote_rune
unsafe { buf.push_many(str_value.str, str_value.len) }
buf << json2.quote_rune
} $else $if T is $map { } $else $if T is $map {
e.encode_map(val, level, mut buf)! e.encode_map(val, level, mut buf)!
} $else $if T is []Any { } $else $if T is $array {
// TODO test e.encode_array(val, level, mut buf)!
e.encode_any(val, level, mut buf)!
} $else $if T is Encodable { } $else $if T is Encodable {
str_value := val.json_str() str_value := val.json_str()
unsafe { buf.push_many(str_value.str, str_value.len) } unsafe { buf.push_many(str_value.str, str_value.len) }
} $else $if T is Null {
unsafe { buf.push_many(json2.null_in_bytes.str, json2.null_in_bytes.len) }
} $else $if T is $struct { } $else $if T is $struct {
e.encode_struct(val, level, mut buf)! e.encode_struct(val, level, mut buf)!
} $else $if T is $enum { } $else $if T is $enum {
@ -180,8 +121,6 @@ fn (e &Encoder) encode_value_with_level[T](val T, level int, mut buf []u8) ! {
} $else $if T is $int || T is $float || T is bool { } $else $if T is $int || T is $float || T is bool {
str_int := val.str() str_int := val.str()
unsafe { buf.push_many(str_int.str, str_int.len) } unsafe { buf.push_many(str_int.str, str_int.len) }
} $else $if T is Null {
unsafe { buf.push_many(json2.null_in_bytes.str, json2.null_in_bytes.len) }
} $else { } $else {
return error('cannot encode value with ${typeof(val).name} type') return error('cannot encode value with ${typeof(val).name} type')
} }
@ -203,6 +142,7 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut buf []u8) ! {
} }
$for field in U.fields { $for field in U.fields {
mut ignore_field := false mut ignore_field := false
value := val.$(field.name) value := val.$(field.name)
is_nil := val.$(field.name).str() == '&nil' is_nil := val.$(field.name).str() == '&nil'
@ -215,10 +155,9 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut buf []u8) ! {
} }
} }
$if field.is_option { $if value is $option {
is_none := value == none workaround := val.$(field.name)
if workaround != none { // smartcast
if !is_none {
e.encode_newline(level, mut buf)! e.encode_newline(level, mut buf)!
if json_name != '' { if json_name != '' {
e.encode_string(json_name, mut buf)! e.encode_string(json_name, mut buf)!
@ -230,61 +169,12 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut buf []u8) ! {
if e.newline != 0 { if e.newline != 0 {
buf << ` ` buf << ` `
} }
e.encode_value_with_level(value, level, mut buf)!
$if field.typ is ?string {
e.encode_string(val.$(field.name) ?.str(), mut buf)!
} $else $if field.typ is ?bool {
if value ?.str() == json2.true_in_string {
unsafe { buf.push_many(json2.true_in_string.str, json2.true_in_string.len) }
} else {
unsafe { buf.push_many(json2.false_in_string.str, json2.false_in_string.len) }
}
} $else $if field.typ is ?f32 || field.typ is ?f64 || field.typ is ?i8
|| field.typ is ?i16 || field.typ is ?int || field.typ is ?i64
|| field.typ is ?u8 || field.typ is ?u16 || field.typ is ?u32
|| field.typ is ?u64 {
str_value := val.$(field.name) ?.str()
unsafe { buf.push_many(str_value.str, str_value.len) }
} $else $if field.typ is ?time.Time {
option_value := val.$(field.name) as ?time.Time
parsed_time := option_value as time.Time
e.encode_string(parsed_time.format_rfc3339(), mut buf)!
} $else $if field.is_array {
e.encode_array(value, level + 1, mut buf)!
} $else $if field.is_struct {
e.encode_struct(value, level + 1, mut buf)!
} $else $if field.is_enum {
// FIXME - checker and cast error
// wr.write(int(val.$(field.name)?).str().bytes())!
return error('type ${typeof(val).name} cannot be encoded yet')
} $else $if field.is_alias {
match field.unaliased_typ {
typeof[string]().idx {
e.encode_string(value.str(), mut buf)!
}
typeof[bool]().idx {}
typeof[f32]().idx, typeof[f64]().idx, typeof[i8]().idx, typeof[i16]().idx,
typeof[int]().idx, typeof[i64]().idx, typeof[u8]().idx, typeof[u16]().idx,
typeof[u32]().idx, typeof[u64]().idx {
str_value := value.str()
unsafe { buf.push_many(str_value.str, str_value.len) }
}
typeof[[]int]().idx {
// FIXME - error: could not infer generic type `U` in call to `encode_array`
// e.encode_array(value, level, mut buf)!
}
else {
// e.encode_value_with_level(value, level + 1, mut buf)!
}
}
} $else {
return error('type ${typeof(val).name} cannot be array encoded')
}
} else { } else {
ignore_field = true ignore_field = true
} }
} $else { } $else {
is_none := val.$(field.name).str() == 'unknown sum type value' is_none := val.$(field.name).str() == 'unknown sum type value' // assert json.encode(StructType[SumTypes]{}) == '{}'
if !is_none && !is_nil { if !is_none && !is_nil {
e.encode_newline(level, mut buf)! e.encode_newline(level, mut buf)!
if json_name != '' { if json_name != '' {
@ -410,15 +300,10 @@ fn (e &Encoder) encode_array[U](val []U, level int, mut buf []u8) ! {
} $else $if U is $struct { } $else $if U is $struct {
e.encode_struct(val[i], level + 1, mut buf)! e.encode_struct(val[i], level + 1, mut buf)!
} $else $if U is $sumtype { } $else $if U is $sumtype {
// TODO test e.encode_value_with_level(val[i], level + 1, mut buf)!
$if U is Any {
e.encode_any(val[i], level + 1, mut buf)!
} $else {
// TODO
}
} $else $if U is $enum { } $else $if U is $enum {
// TODO test // TODO test
e.encode_any(i64(val[i]), level + 1, mut buf)! e.encode_value_with_level(val[i], level + 1, mut buf)!
} $else { } $else {
return error('type ${typeof(val).name} cannot be array encoded') return error('type ${typeof(val).name} cannot be array encoded')
} }

View File

@ -179,4 +179,10 @@ fn test_encode_time() {
assert json.encode({ assert json.encode({
'bro': json.Any(time.Time{}) 'bro': json.Any(time.Time{})
}) == '{"bro":"0000-00-00T00:00:00.000Z"}' }) == '{"bro":"0000-00-00T00:00:00.000Z"}'
assert json.encode({
'bro': time.Time{}
}) == '{"bro":"0000-00-00T00:00:00.000Z"}'
assert json.encode(time.Time{}) == '"0000-00-00T00:00:00.000Z"'
} }

View File

@ -18,7 +18,7 @@ fn test_fast_raw_decode() {
s := '{"name":"Peter","age":28,"salary":95000.5,"title":2}' s := '{"name":"Peter","age":28,"salary":95000.5,"title":2}'
o := json.fast_raw_decode(s) or { o := json.fast_raw_decode(s) or {
assert false assert false
json.Any(json.null) json.Any('')
} }
str := o.str() str := o.str()
assert str == '{"name":"Peter","age":"28","salary":"95000.5","title":"2"}' assert str == '{"name":"Peter","age":"28","salary":"95000.5","title":"2"}'

View File

@ -272,7 +272,6 @@ pub struct Association {
price APrice price APrice
} }
//! FIX: returning null
fn test_encoding_struct_with_pointers() { fn test_encoding_struct_with_pointers() {
value := Association{ value := Association{
association: &Association{ association: &Association{
@ -299,10 +298,11 @@ struct Data {
mut: mut:
countries []Country countries []Country
users map[string]User users map[string]User
extra map[string]map[string]int
} }
fn test_nested_type() { 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"}}}' 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{ data := Data{
countries: [ countries: [
Country{ Country{
@ -332,7 +332,111 @@ fn test_nested_type() {
pets: 'little boo' 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) out := json.encode(data)
assert out == data_expected assert out == data_expected
} }
struct Human {
name string
}
struct Item {
tag string
}
enum Animal {
dog // Will be encoded as `0`
cat
}
type Entity = Animal | Human | Item | string | time.Time
struct SomeGame {
title string
player Entity
other []Entity
}
fn test_encode_decode_sumtype() {
t := time.now()
game := SomeGame{
title: 'Super Mega Game'
player: Human{'Monke'}
other: [
Entity(Item{'Pen'}),
Item{'Cookie'},
Animal.cat,
'Stool',
t,
]
}
enc := json.encode(game)
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke"},"other":[{"tag":"Pen"},{"tag":"Cookie"},1,"Stool","${t.format_rfc3339()}"]}'
}
struct Foo3 {
name string
age int @[omitempty]
}
// fn test_omit_empty() {
// foo := Foo3{'Bob', 0}
// assert json.encode(foo) == '{"name":"Bob"}'
// assert json.encode_pretty(foo) == '{
// "name": "Bob"
// }'
// }
struct Foo31 {
name string
age ?int
}
fn test_option_instead_of_omit_empty() {
foo := Foo31{
name: 'Bob'
}
assert json.encode_pretty(foo) == '{
"name": "Bob"
}'
}
struct Asdasd {
data GamePacketData
}
type GamePacketData = GPEquipItem | GPScale
struct GPScale {
value f32
}
struct GPEquipItem {
name string
}
fn create_game_packet(data &GamePacketData) string {
return json.encode(data)
}
// fn test_encode_sumtype_defined_ahead() {
// ret := create_game_packet(&GamePacketData(GPScale{}))
// assert ret == '{"value":0}'
// }

View File

@ -55,7 +55,7 @@ struct Item {
} }
enum Animal { enum Animal {
dog // Will be encoded as `0` dog
cat cat
} }
@ -69,22 +69,7 @@ struct SomeGame {
//! BUGFIX - .from_json(res) //! BUGFIX - .from_json(res)
fn test_encode_decode_sumtype() { fn test_encode_decode_sumtype() {
t := time.now() enc := '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},1,"Stool",{"_type":"Time","value":${t.unix_time()}}]}'
game := SomeGame{
title: 'Super Mega Game'
player: Human{'Monke'}
other: [
Entity(Item{'Pen'}),
Item{'Cookie'},
Animal.cat,
'Stool',
t,
]
}
enc := json.encode(game)
assert enc == '{"title":"Super Mega Game","player":{"name":"Monke","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},1,"Stool",{"_type":"Time","value":${t.unix_time()}}]}'
dec := json.decode[SomeGame](enc)! dec := json.decode[SomeGame](enc)!
@ -137,6 +122,8 @@ fn test_encode_decode_time() {
assert s.contains('"reg_date":1608621780') assert s.contains('"reg_date":1608621780')
user2 := json.decode[User2](s)! user2 := json.decode[User2](s)!
assert user2.reg_date.str() == '2020-12-22 07:23:00' assert user2.reg_date.str() == '2020-12-22 07:23:00'
}
struct City { struct City {
name string name string
} }
@ -220,8 +207,7 @@ fn test_nested_type() {
} }
} }
} }
out := json.encode(data)
assert out == data_expected
data2 := json.decode[Data](data_expected)! data2 := json.decode[Data](data_expected)!
assert data2.countries.len == data.countries.len assert data2.countries.len == data.countries.len
for i in 0 .. 1 { for i in 0 .. 1 {
@ -330,40 +316,3 @@ fn test_decode_missing_maps_field() {
assert '${info.items}' == '[]' assert '${info.items}' == '[]'
assert '${info.maps}' == '{}' assert '${info.maps}' == '{}'
} }
struct Foo3 {
name string
age int @[omitempty]
}
//! BUGFIX - .from_json(res)
fn test_omit_empty() {
foo := Foo3{'Bob', 0}
assert json.encode_pretty(foo) == '{
"name": "Bob"
}'
}
struct Asdasd {
data GamePacketData
}
type GamePacketData = GPEquipItem | GPScale
struct GPScale {
value f32
}
struct GPEquipItem {
name string
}
fn create_game_packet(data &GamePacketData) string {
return json.encode(data)
}
//! FIX: returning null
fn test_encode_sumtype_defined_ahead() {
ret := create_game_packet(&GamePacketData(GPScale{}))
assert ret == '{"value":0,"_type":"GPScale"}'
}