From b6c2aab0922397d223fad2a2624980edddf13a89 Mon Sep 17 00:00:00 2001 From: Hitalo Souza <63821277+enghitalo@users.noreply.github.com> Date: Fri, 9 Dec 2022 13:08:24 -0300 Subject: [PATCH] json2: support encoding of optional struct fields (#16521) --- vlib/x/json2/encoder.v | 178 ++++++++++++++++++++++++-------------- vlib/x/json2/json2.v | 17 +++- vlib/x/json2/json2_test.v | 141 +++++++++++++++++++++++++----- vlib/x/json2/types.v | 1 - 4 files changed, 247 insertions(+), 90 deletions(-) diff --git a/vlib/x/json2/encoder.v b/vlib/x/json2/encoder.v index e7720b3646..bb935b84ca 100644 --- a/vlib/x/json2/encoder.v +++ b/vlib/x/json2/encoder.v @@ -109,20 +109,6 @@ fn (e &Encoder) encode_any(val Any, level int, mut wr io.Writer) ! { wr.write(json2.comma_bytes)! } } - - e.encode_newline(level - 1, mut wr)! - wr.write([u8(`]`)])! - } - []int { - wr.write([u8(`[`)])! - for i in 0 .. val.len { - e.encode_newline(level, mut wr)! - e.encode_value_with_level(val[i], level + 1, mut wr)! - if i < val.len - 1 { - wr.write(json2.comma_bytes)! - } - } - e.encode_newline(level - 1, mut wr)! wr.write([u8(`]`)])! } @@ -147,9 +133,6 @@ fn (e &Encoder) encode_value_with_level[T](val T, level int, mut wr io.Writer) ! e.encode_any(val, level, mut wr)! } $else $if T is Encodable { wr.write(val.json_str().bytes())! - } $else $if T is []int { - // wr.write(val.str)! - e.encode_any(val, level, mut wr)! } $else $if T is $Struct { e.encode_struct(val, level, mut wr)! } $else $if T is $Enum { @@ -164,58 +147,100 @@ fn (e &Encoder) encode_struct[U](val U, level int, mut wr io.Writer) ! { wr.write([u8(`{`)])! mut i := 0 mut fields_len := 0 - $for _ in U.fields { - fields_len++ + $for field in U.fields { + value := val.$(field.name) + if value.str() != 'Option(error: none)' { + fields_len++ + } } $for field in U.fields { - mut json_name := '' - for attr in field.attrs { - if attr.contains('json: ') { - json_name = attr.replace('json: ', '') - break - } - } - e.encode_newline(level, mut wr)! - if json_name != '' { - e.encode_string(json_name, mut wr)! - } else { - e.encode_string(field.name, mut wr)! - } - wr.write(json2.colon_bytes)! - if e.newline != 0 { - wr.write(json2.space_bytes)! - } - if typeof(val.$(field.name)).name.contains('?') { - if field.typ == 20 { - if val.$(field.name).str() == 'Option(error: none)' { - // TODO? - } else { - e.encode_string(val.$(field.name).str().replace("Option('", '').trim_string_right("')"), mut - wr)! + value := val.$(field.name) + is_none := value.str() == 'Option(error: none)' + if !is_none { + mut json_name := '' + for attr in field.attrs { + if attr.contains('json: ') { + json_name = attr.replace('json: ', '') + break } } - } else { - match field.unaliased_typ { - typeof[string]().idx { - e.encode_string(val.$(field.name).str(), mut wr)! - } - typeof[int]().idx { - wr.write(val.$(field.name).str().bytes())! - } - typeof[[]byte]().idx { - //! array - e.encode_array(val.$(field.name), level, mut wr)! - } - else { - field_value := val.$(field.name) - e.encode_value_with_level(field_value, level + 1, mut wr)! + e.encode_newline(level, mut wr)! + if json_name != '' { + e.encode_string(json_name, mut wr)! + } else { + e.encode_string(field.name, mut wr)! + } + wr.write(json2.colon_bytes)! + if e.newline != 0 { + wr.write(json2.space_bytes)! + } + $if field.typ is string { + e.encode_string(value.str(), mut wr)! + } $else $if field.typ is bool || 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 { + wr.write(value.str().bytes())! + } $else $if field.typ is []string || field.typ is []bool || 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 []byte || field.typ is []u16 || field.typ is []u32 + || field.typ is []u64 { + e.encode_array(value, level, mut wr)! + } $else { + } + $if field.typ is ?string { + optional_value := val.$(field.name) as ?string + e.encode_string(optional_value, mut wr)! + } $else $if field.typ is ?bool { + optional_value := val.$(field.name) as ?bool + wr.write(Any(optional_value).str().bytes())! + } $else $if field.typ is ?f32 { + optional_value := val.$(field.name) as ?f32 + wr.write(Any(optional_value).str().bytes())! + } $else $if field.typ is ?f64 { + optional_value := val.$(field.name) as ?f64 + wr.write(Any(optional_value).str().bytes())! + } $else $if field.typ is ?i8 { + optional_value := val.$(field.name) as ?i8 + wr.write(Any(optional_value).str().bytes())! + } $else $if field.typ is ?i16 { + optional_value := val.$(field.name) as ?i16 + wr.write(Any(optional_value).str().bytes())! + } $else $if field.typ is ?int { + optional_value := val.$(field.name) as ?int + wr.write(Any(optional_value).int().str().bytes())! + } $else $if field.typ is ?[]byte { + optional_value := val.$(field.name) as ?[]byte + e.encode_array(optional_value, level, mut wr)! + } $else $if field.typ is ?[]int { + optional_value := val.$(field.name) as ?[]int + e.encode_array(optional_value, level, mut wr)! + } $else { + if field.unaliased_typ != field.typ { + match field.unaliased_typ { + typeof[string]().idx { + e.encode_string(value.str(), mut wr)! + } + 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 { + wr.write(value.str().bytes())! + } + typeof[[]byte]().idx, typeof[[]int]().idx { + e.encode_array(value, level, mut wr)! + } + else { + // e.encode_value_with_level(value, level + 1, mut wr)! + } + } } } + + if i < fields_len - 1 { + wr.write(json2.comma_bytes)! + } + i++ } - if i < fields_len - 1 { - wr.write(json2.comma_bytes)! - } - i++ } e.encode_newline(level - 1, mut wr)! wr.write([u8(`}`)])! @@ -226,7 +251,34 @@ fn (e &Encoder) encode_array[U](val U, level int, mut wr io.Writer) ! { wr.write([u8(`[`)])! for i in 0 .. val.len { e.encode_newline(level, mut wr)! - e.encode_value_with_level(val[i], level + 1, mut wr)! + + $if U is []string { + e.encode_any(val[i], level + 1, mut wr)! + } $else $if U is []bool { + e.encode_any(bool(val[i]), level + 1, mut wr)! + } $else $if U is []f32 { + e.encode_any(f32(val[i]), level + 1, mut wr)! + } $else $if U is []f64 { + e.encode_any(f64(val[i]), level + 1, mut wr)! + } $else $if U is []i8 { + e.encode_any(i8(val[i]), level + 1, mut wr)! + } $else $if U is []i16 { + e.encode_any(i16(val[i]), level + 1, mut wr)! + } $else $if U is []int { + e.encode_any(int(val[i]), level + 1, mut wr)! + } $else $if U is []i64 { + e.encode_any(i64(val[i]), level + 1, mut wr)! + } $else $if U is []u8 { + e.encode_any(u8(val[i]), level + 1, mut wr)! + } $else $if U is []byte { + e.encode_any(u8(val[i]), level + 1, mut wr)! + } $else $if U is []u16 { + e.encode_any(u16(val[i]), level + 1, mut wr)! + } $else $if U is []u32 { + e.encode_any(u32(val[i]), level + 1, mut wr)! + } $else $if U is []u64 { + e.encode_any(u64(val[i]), level + 1, mut wr)! + } if i < val.len - 1 { wr.write(json2.comma_bytes)! } diff --git a/vlib/x/json2/json2.v b/vlib/x/json2/json2.v index 2e6efee063..7989c2f5ec 100644 --- a/vlib/x/json2/json2.v +++ b/vlib/x/json2/json2.v @@ -64,9 +64,20 @@ pub fn encode[T](val T) string { defer { unsafe { sb.free() } } - default_encoder.encode_value(val, mut sb) or { - dump(err) - default_encoder.encode_value[Null](null, mut sb) or {} + $if T is $Array { + mut array_of_any := []Any{} + for value in val { + array_of_any << value + } + default_encoder.encode_value(array_of_any, mut sb) or { + dump(err) + default_encoder.encode_value[Null](null, mut sb) or {} + } + } $else { + default_encoder.encode_value(val, mut sb) or { + dump(err) + default_encoder.encode_value[Null](null, mut sb) or {} + } } return sb.str() } diff --git a/vlib/x/json2/json2_test.v b/vlib/x/json2/json2_test.v index b7c48053e3..ad42f95084 100644 --- a/vlib/x/json2/json2_test.v +++ b/vlib/x/json2/json2_test.v @@ -15,33 +15,29 @@ pub mut: title JobTitle } -struct EmployeeOp { +struct OptionalStruct { pub mut: - name ?string = none + name string last_name ?string = none - age ?int - salary f32 - title JobTitle + age ?int = none + salary ?f32 = none } -fn (e Employee) to_json() string { - mut mp := map[string]json.Any{} - mp['name'] = json.Any(e.name) - mp['age'] = json.Any(e.age) - mp['salary'] = json.Any(e.salary) - mp['title'] = json.Any(int(e.title)) - /* - $for field in Employee.fields { - d := e.$(field.name) - - $if field.typ is JobTitle { - mp[field.name] = json.encode(d) - } $else { - mp[field.name] = d - } +fn test_simple_optional() { + x := OptionalStruct{ + name: 'Peter' } - */ - return mp.str() + s := json.encode[OptionalStruct](x) + assert s == '{"name":"Peter"}' + // y := json.decode(s) or { + // println(err) + // assert false + // return + // } + // assert y.name == 'Peter' + // assert y.age == 28 + // assert y.salary == 95000.5 + // assert y.title == .worker } // ! BUGFIX @@ -99,8 +95,13 @@ mut: val T } +struct MultTypeTestOptional[T] { +mut: + val ?T +} + // NOTE - This can substitute a lot of others tests -fn test_bool_decode() { +fn test_mult_decode() { assert json.decode[MultTypeTest[bool]]('{"val": ""}')!.val == false assert json.decode[MultTypeTest[bool]]('{"val": "0"}')!.val == false assert json.decode[MultTypeTest[bool]]('{"val": "1"}')!.val == true @@ -124,4 +125,98 @@ fn test_bool_decode() { assert json.decode[MultTypeTest[int]]('{"val": "false"}')!.val == 0 assert json.decode[MultTypeTest[int]]('{"val": true}')!.val == 1 assert json.decode[MultTypeTest[int]]('{"val": false}')!.val == 0 + + assert json.decode[MultTypeTest[string]]('{"val": ""}')!.val == '' + assert json.decode[MultTypeTest[string]]('{"val": "0"}')!.val == '0' + assert json.decode[MultTypeTest[string]]('{"val": "1"}')!.val == '1' + assert json.decode[MultTypeTest[string]]('{"val": "2"}')!.val == '2' + assert json.decode[MultTypeTest[string]]('{"val": 0}')!.val == '0' + assert json.decode[MultTypeTest[string]]('{"val": 1}')!.val == '1' + assert json.decode[MultTypeTest[string]]('{"val": 2}')!.val == '2' + assert json.decode[MultTypeTest[string]]('{"val": "true"}')!.val == 'true' + assert json.decode[MultTypeTest[string]]('{"val": "false"}')!.val == 'false' + assert json.decode[MultTypeTest[string]]('{"val": true}')!.val == 'true' + assert json.decode[MultTypeTest[string]]('{"val": false}')!.val == 'false' + + // assert json.decode[MultTypeTestOptional[string]]('{"val": ""}')! == MultTypeTestOptional[string]{val: ""} + /* + assert json.decode[MultTypeTestOptional[string]]('{"val": "0"}')!.val == "0" + assert json.decode[MultTypeTestOptional[string]]('{"val": "1"}')!.val == "1" + assert json.decode[MultTypeTestOptional[string]]('{"val": "2"}')!.val == "2" + assert json.decode[MultTypeTestOptional[string]]('{"val": 0}')!.val == "0" + assert json.decode[MultTypeTestOptional[string]]('{"val": 1}')!.val == "1" + assert json.decode[MultTypeTestOptional[string]]('{"val": 2}')!.val == "2" + assert json.decode[MultTypeTestOptional[string]]('{"val": "true"}')!.val == "true" + assert json.decode[MultTypeTestOptional[string]]('{"val": "false"}')!.val == "false" + assert json.decode[MultTypeTestOptional[string]]('{"val": true}')!.val == "true" + assert json.decode[MultTypeTestOptional[string]]('{"val": false}')!.val == "false" + */ +} + +fn test_mult_encode() { + assert json.encode(MultTypeTest[[]string]{}) == '{"val":[]}' + assert json.encode(MultTypeTest[[]string]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTest[[]string]{ val: ['0'] }) == '{"val":["0"]}' + assert json.encode(MultTypeTest[[]string]{ val: ['1'] }) == '{"val":["1"]}' + + assert json.encode(MultTypeTest[bool]{}) == '{"val":false}' + assert json.encode(MultTypeTest[bool]{ val: false }) == '{"val":false}' + assert json.encode(MultTypeTest[bool]{ val: true }) == '{"val":true}' + + assert json.encode(MultTypeTestOptional[bool]{ val: none }) == '{}' + assert json.encode(MultTypeTestOptional[bool]{}) == '{"val":false}' + assert json.encode(MultTypeTestOptional[bool]{ val: false }) == '{"val":false}' + assert json.encode(MultTypeTestOptional[bool]{ val: true }) == '{"val":true}' + + assert json.encode(MultTypeTest[int]{}) == '{"val":0}' + assert json.encode(MultTypeTest[int]{ val: 0 }) == '{"val":0}' + assert json.encode(MultTypeTest[int]{ val: 1 }) == '{"val":1}' + + assert json.encode(MultTypeTestOptional[int]{ val: none }) == '{}' + assert json.encode(MultTypeTestOptional[int]{}) == '{"val":0}' + assert json.encode(MultTypeTestOptional[int]{ val: 0 }) == '{"val":0}' + assert json.encode(MultTypeTestOptional[int]{ val: 1 }) == '{"val":1}' + + assert json.encode(MultTypeTest[[]int]{}) == '{"val":[]}' + assert json.encode(MultTypeTest[[]int]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTest[[]int]{ val: [0] }) == '{"val":[0]}' + assert json.encode(MultTypeTest[[]int]{ val: [1] }) == '{"val":[1]}' + assert json.encode(MultTypeTest[[]int]{ val: [0, 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}' + + assert json.encode(MultTypeTestOptional[[]int]{ val: none }) == '{}' + assert json.encode(MultTypeTestOptional[[]int]{}) == '{"val":[]}' + assert json.encode(MultTypeTestOptional[[]int]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTestOptional[[]int]{ val: [0] }) == '{"val":[0]}' + assert json.encode(MultTypeTestOptional[[]int]{ val: [1] }) == '{"val":[1]}' + assert json.encode(MultTypeTestOptional[[]int]{ val: [0, 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}' + + assert json.encode(MultTypeTest[[]byte]{}) == '{"val":[]}' + assert json.encode(MultTypeTest[[]byte]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTest[[]byte]{ val: [byte(0)] }) == '{"val":[0]}' + assert json.encode(MultTypeTest[[]byte]{ val: [byte(1)] }) == '{"val":[1]}' + assert json.encode(MultTypeTest[[]byte]{ val: [byte(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}' + + assert json.encode(MultTypeTest[[]i64]{}) == '{"val":[]}' + assert json.encode(MultTypeTest[[]i64]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTest[[]i64]{ val: [i64(0)] }) == '{"val":[0]}' + assert json.encode(MultTypeTest[[]i64]{ val: [i64(1)] }) == '{"val":[1]}' + assert json.encode(MultTypeTest[[]i64]{ val: [i64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}' + + assert json.encode(MultTypeTest[[]u64]{}) == '{"val":[]}' + assert json.encode(MultTypeTest[[]u64]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTest[[]u64]{ val: [u64(0)] }) == '{"val":[0]}' + assert json.encode(MultTypeTest[[]u64]{ val: [u64(1)] }) == '{"val":[1]}' + assert json.encode(MultTypeTest[[]u64]{ val: [u64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0,1,0,2,3,2,5,1]}' + + assert json.encode(MultTypeTest[[]f64]{}) == '{"val":[]}' + assert json.encode(MultTypeTest[[]f64]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTest[[]f64]{ val: [f64(0)] }) == '{"val":[0.0]}' + assert json.encode(MultTypeTest[[]f64]{ val: [f64(1)] }) == '{"val":[1.0]}' + assert json.encode(MultTypeTest[[]f64]{ val: [f64(0), 1, 0, 2, 3, 2, 5, 1] }) == '{"val":[0.0,1.0,0.0,2.0,3.0,2.0,5.0,1.0]}' + + assert json.encode(MultTypeTest[[]bool]{}) == '{"val":[]}' + assert json.encode(MultTypeTest[[]bool]{ val: [] }) == '{"val":[]}' + assert json.encode(MultTypeTest[[]bool]{ val: [true] }) == '{"val":[true]}' + assert json.encode(MultTypeTest[[]bool]{ val: [false] }) == '{"val":[false]}' + assert json.encode(MultTypeTest[[]bool]{ val: [false, true, false] }) == '{"val":[false,true,false]}' } diff --git a/vlib/x/json2/types.v b/vlib/x/json2/types.v index 272e698761..6e5dc50ddb 100644 --- a/vlib/x/json2/types.v +++ b/vlib/x/json2/types.v @@ -3,7 +3,6 @@ module json2 // `Any` is a sum type that lists the possible types to be decoded and used. pub type Any = Null | []Any - | []int | bool | f32 | f64