From 62872c677fff03c5f5ca5c1b35329ab4ff3cbda5 Mon Sep 17 00:00:00 2001 From: Hitalo Souza <63821277+enghitalo@users.noreply.github.com> Date: Fri, 12 Jan 2024 16:48:28 -0400 Subject: [PATCH] json2: cleanup (#20347) --- vlib/time/format.v | 1 + vlib/x/json2/any_test.v | 2 +- vlib/x/json2/decoder.v | 4 +- vlib/x/json2/encode_struct_test.v | 2 +- vlib/x/json2/encoder.v | 189 ++++-------------- vlib/x/json2/encoder_test.v | 6 + vlib/x/json2/json2_test.v | 2 +- .../json_test.v | 108 +++++++++- .../json_todo_test.vv | 61 +----- 9 files changed, 161 insertions(+), 214 deletions(-) diff --git a/vlib/time/format.v b/vlib/time/format.v index 78fd3fb54e..9c7a24a1bf 100644 --- a/vlib/time/format.v +++ b/vlib/time/format.v @@ -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' diff --git a/vlib/x/json2/any_test.v b/vlib/x/json2/any_test.v index 644c104bf1..9eb51569ab 100644 --- a/vlib/x/json2/any_test.v +++ b/vlib/x/json2/any_test.v @@ -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}}' } diff --git a/vlib/x/json2/decoder.v b/vlib/x/json2/decoder.v index 311accc0b7..6464c65e5e 100644 --- a/vlib/x/json2/decoder.v +++ b/vlib/x/json2/decoder.v @@ -185,7 +185,9 @@ fn (mut p Parser) decode_value() !Any { } } } - return Any(null) + return InvalidTokenError{ + token: p.tok + } } @[manualfree] diff --git a/vlib/x/json2/encode_struct_test.v b/vlib/x/json2/encode_struct_test.v index 0749238d0f..7c4f782a8e 100644 --- a/vlib/x/json2/encode_struct_test.v +++ b/vlib/x/json2/encode_struct_test.v @@ -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"}' diff --git a/vlib/x/json2/encoder.v b/vlib/x/json2/encoder.v index d4744b6117..5f53173edb 100644 --- a/vlib/x/json2/encoder.v +++ b/vlib/x/json2/encoder.v @@ -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') } diff --git a/vlib/x/json2/encoder_test.v b/vlib/x/json2/encoder_test.v index 13d1088705..30bdc2c3dc 100644 --- a/vlib/x/json2/encoder_test.v +++ b/vlib/x/json2/encoder_test.v @@ -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"' } diff --git a/vlib/x/json2/json2_test.v b/vlib/x/json2/json2_test.v index 8761232712..97e1c7bb88 100644 --- a/vlib/x/json2/json2_test.v +++ b/vlib/x/json2/json2_test.v @@ -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"}' diff --git a/vlib/x/json2/json_module_compatibility_test/json_test.v b/vlib/x/json2/json_module_compatibility_test/json_test.v index e9c00920cf..4b14adbd8a 100644 --- a/vlib/x/json2/json_module_compatibility_test/json_test.v +++ b/vlib/x/json2/json_module_compatibility_test/json_test.v @@ -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}' +// } diff --git a/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv b/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv index 310bfc5e79..c75135d863 100644 --- a/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv +++ b/vlib/x/json2/json_module_compatibility_test/json_todo_test.vv @@ -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"}' -}