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)
// 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.
@[markused]
pub fn (t Time) format_rfc3339() string {
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'

View File

@ -123,6 +123,6 @@ fn test_str() {
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 { 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}}'
}

View File

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

View File

@ -253,7 +253,7 @@ fn test_pointer() {
}
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: '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) ! {
buf << json2.curly_open_rune
mut idx := 0
@ -135,7 +61,18 @@ fn (e &Encoder) encode_map[T](value T, level int, mut buf []u8) ! {
if e.newline != 0 {
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 {
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) ! {
$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)!
} $else $if T is Any {
e.encode_any(val, level, mut buf)!
} $else $if T is $sumtype {
if val.str() != 'unknown sum type value' {
$for v in val.variants {
if val is v {
e.encode_value_with_level(val, level, mut buf)!
}
$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 {
parsed_time := time.parse(val.str()) or { time.Time{} }
e.encode_string(parsed_time.format_rfc3339(), mut buf)!
str_value := val.format_rfc3339()
buf << json2.quote_rune
unsafe { buf.push_many(str_value.str, str_value.len) }
buf << json2.quote_rune
} $else $if T is $map {
e.encode_map(val, level, mut buf)!
} $else $if T is []Any {
// TODO test
e.encode_any(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(json2.null_in_bytes.str, json2.null_in_bytes.len) }
} $else $if T is $struct {
e.encode_struct(val, level, mut buf)!
} $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 {
str_int := val.str()
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 {
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 {
mut ignore_field := false
value := val.$(field.name)
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 {
is_none := value == none
if !is_none {
$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)!
@ -230,61 +169,12 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut buf []u8) ! {
if e.newline != 0 {
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')
}
e.encode_value_with_level(value, level, mut buf)!
} else {
ignore_field = true
}
} $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 {
e.encode_newline(level, mut buf)!
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 {
e.encode_struct(val[i], level + 1, mut buf)!
} $else $if U is $sumtype {
// TODO test
$if U is Any {
e.encode_any(val[i], level + 1, mut buf)!
} $else {
// TODO
}
e.encode_value_with_level(val[i], level + 1, mut buf)!
} $else $if U is $enum {
// TODO test
e.encode_any(i64(val[i]), level + 1, mut buf)!
e.encode_value_with_level(val[i], level + 1, mut buf)!
} $else {
return error('type ${typeof(val).name} cannot be array encoded')
}

View File

@ -179,4 +179,10 @@ fn test_encode_time() {
assert json.encode({
'bro': json.Any(time.Time{})
}) == '{"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}'
o := json.fast_raw_decode(s) or {
assert false
json.Any(json.null)
json.Any('')
}
str := o.str()
assert str == '{"name":"Peter","age":"28","salary":"95000.5","title":"2"}'

View File

@ -272,7 +272,6 @@ pub struct Association {
price APrice
}
//! FIX: returning null
fn test_encoding_struct_with_pointers() {
value := Association{
association: &Association{
@ -299,10 +298,11 @@ struct Data {
mut:
countries []Country
users map[string]User
extra map[string]map[string]int
}
fn test_nested_type() {
data_expected := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":{"Foo":{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},"Boo":{"age":20,"nums":[5,3,1],"lastName":"Smith","IsRegistered":false,"type":4,"pet_animals":"little boo"}}}'
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{
@ -332,7 +332,111 @@ fn test_nested_type() {
pets: 'little boo'
}
}
extra: {
'2': {
'n1': 2
'n2': 4
'n3': 8
'n4': 16
}
'3': {
'n1': 3
'n2': 9
'n3': 27
'n4': 81
}
}
}
out := json.encode(data)
assert out == data_expected
}
struct Human {
name string
}
struct Item {
tag string
}
enum Animal {
dog // Will be encoded as `0`
cat
}
type Entity = Animal | Human | Item | string | time.Time
struct SomeGame {
title string
player Entity
other []Entity
}
fn test_encode_decode_sumtype() {
t := time.now()
game := SomeGame{
title: 'Super Mega Game'
player: Human{'Monke'}
other: [
Entity(Item{'Pen'}),
Item{'Cookie'},
Animal.cat,
'Stool',
t,
]
}
enc := json.encode(game)
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 {
dog // Will be encoded as `0`
dog
cat
}
@ -69,22 +69,7 @@ struct SomeGame {
//! BUGFIX - .from_json(res)
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","_type":"Human"},"other":[{"tag":"Pen","_type":"Item"},{"tag":"Cookie","_type":"Item"},1,"Stool",{"_type":"Time","value":${t.unix_time()}}]}'
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)!
@ -137,6 +122,8 @@ fn test_encode_decode_time() {
assert s.contains('"reg_date":1608621780')
user2 := json.decode[User2](s)!
assert user2.reg_date.str() == '2020-12-22 07:23:00'
}
struct City {
name string
}
@ -220,8 +207,7 @@ fn test_nested_type() {
}
}
}
out := json.encode(data)
assert out == data_expected
data2 := json.decode[Data](data_expected)!
assert data2.countries.len == data.countries.len
for i in 0 .. 1 {
@ -330,40 +316,3 @@ fn test_decode_missing_maps_field() {
assert '${info.items}' == '[]'
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"}'
}