From fafe30b6aa356f36e56100968ebad884ac4716fc Mon Sep 17 00:00:00 2001 From: Xavier B Date: Sat, 5 Dec 2020 10:13:18 -0500 Subject: [PATCH] json: add support for aliased struct fields (#6556) --- vlib/json/json_test.v | 44 +++++++++------ vlib/v/gen/json.v | 123 +++++++++++++++++++++++++++--------------- 2 files changed, 110 insertions(+), 57 deletions(-) diff --git a/vlib/json/json_test.v b/vlib/json/json_test.v index d49827692f..221c425608 100644 --- a/vlib/json/json_test.v +++ b/vlib/json/json_test.v @@ -171,7 +171,6 @@ struct Data { 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"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' - data := Data{ countries: [ Country{ @@ -195,7 +194,7 @@ fn test_nested_type() { is_registered: true typ: 0 pets: 'little foo' - }, + } 'Boo': User{ age: 20 nums: [5, 3, 1] @@ -204,39 +203,37 @@ fn test_nested_type() { typ: 4 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) println(out) assert out == data_expected - data2 := json.decode(Data, data_expected) or { assert false Data{} } assert data2.countries.len == data.countries.len - for i in 0..1 { + for i in 0 .. 1 { assert data2.countries[i].name == data.countries[i].name assert data2.countries[i].cities.len == data.countries[i].cities.len - for j in 0..1 { + for j in 0 .. 1 { assert data2.countries[i].cities[j].name == data.countries[i].cities[j].name } } - for key, user in data.users { assert data2.users[key].age == user.age assert data2.users[key].nums == user.nums @@ -245,7 +242,6 @@ fn test_nested_type() { assert data2.users[key].typ == user.typ // assert data2.users[key].pets == user.pets // TODO FIX } - for k, v in data.extra { for k2, v2 in v { assert data2.extra[k][k2] == v2 @@ -263,11 +259,9 @@ fn test_generic_struct() { foo_int := Foo{'bar', 12} foo_enc := json.encode(foo_int) assert foo_enc == '{"name":"bar","data":12}' - foo_dec := json.decode(Foo, foo_enc) or { exit(1) } - assert foo_dec.name == 'bar' assert foo_dec.data == 12 } @@ -275,7 +269,6 @@ fn test_generic_struct() { fn test_errors() { invalid_array := fn () { data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":{"name":"Donlon"},"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}}}' - json.decode(Data, data) or { println(err) assert err.starts_with('Json element is not an array:') @@ -283,9 +276,8 @@ fn test_errors() { } assert false } - invalid_object := fn() { + invalid_object := fn () { data := '{"countries":[{"cities":[{"name":"London"},{"name":"Manchester"}],"name":"UK"},{"cities":[{"name":"Donlon"},{"name":"Termanches"}],"name":"KU"}],"users":[{"age":10,"nums":[1,2,3],"lastName":"Johnson","IsRegistered":true,"type":0,"pet_animals":"little foo"},{"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}}}' - json.decode(Data, data) or { println(err) assert err.starts_with('Json element is not an object:') @@ -296,3 +288,25 @@ fn test_errors() { invalid_array() invalid_object() } + +type ID = string + +struct Message { + id ID +} + +fn test_decode_alias_struct() { + msg := json.decode(Message, '{"id": "118499178790780929"}') or { + assert false + Message{} + } + // hacky way of comparing aliased strings + assert msg.id.str() == '118499178790780929' +} + +fn test_encode_alias_struct() { + expected := '{"id":"118499178790780929"}' + msg := Message{'118499178790780929'} + out := json.encode(msg) + assert out == expected +} diff --git a/vlib/v/gen/json.v b/vlib/v/gen/json.v index b965544529..88fc0ba4b3 100644 --- a/vlib/v/gen/json.v +++ b/vlib/v/gen/json.v @@ -43,7 +43,6 @@ fn (mut g Gen) gen_json_for_type(typ table.Type) { g.register_optional(utyp) dec_fn_dec := 'Option_$styp ${dec_fn_name}(cJSON* root)' dec.writeln(' -//Option_$styp ${dec_fn_name}(cJSON* root, $styp* res) { $dec_fn_dec { $styp res; if (!root) { @@ -80,53 +79,26 @@ $enc_fn_dec { g.gen_json_for_type(m.value_type) dec.writeln(g.decode_map(m.key_type, m.value_type)) enc.writeln(g.encode_map(m.key_type, m.value_type)) + } else if sym.kind == .alias { + a := sym.info as table.Alias + parent_typ := a.parent_type + psym := g.table.get_type_symbol(parent_typ) + if is_js_prim(g.typ(parent_typ)) { + g.gen_json_for_type(parent_typ) + return + } + enc.writeln('\to = cJSON_CreateObject();') + if psym.info !is table.Struct { + verror('json: $sym.name is not struct') + } + g.gen_struct_enc_dec(psym.info, styp, mut enc, mut dec) } else { enc.writeln('\to = cJSON_CreateObject();') // Structs. Range through fields if sym.info !is table.Struct { verror('json: $sym.name is not struct') } - info := sym.info as table.Struct - for field in info.fields { - if field.attrs.contains('skip') { - continue - } - mut name := field.name - for attr in field.attrs { - if attr.name == 'json' { - name = attr.arg - break - } - } - field_type := g.typ(field.typ) - if field.attrs.contains('raw') { - dec.writeln('\tres.${c_name(field.name)} = tos2(cJSON_PrintUnformatted(' + 'js_get(root, "$name")));') - } else { - // Now generate decoders for all field types in this struct - // need to do it here so that these functions are generated first - g.gen_json_for_type(field.typ) - dec_name := js_dec_name(field_type) - if is_js_prim(field_type) { - dec.writeln('\tres.${c_name(field.name)} = $dec_name (js_get(root, "$name"));') - } else if g.table.get_type_symbol(field.typ).kind == .enum_ { - dec.writeln('\tres.${c_name(field.name)} = json__decode_u64(js_get(root, "$name"));') - } else { - // dec.writeln(' $dec_name (js_get(root, "$name"), & (res . $field.name));') - tmp := g.new_tmp_var() - dec.writeln('\tOption_$field_type $tmp = $dec_name (js_get(root,"$name"));') - dec.writeln('\tif(!${tmp}.ok) {') - dec.writeln('\t\treturn *(Option_$styp*) &$tmp;') - dec.writeln('\t}') - dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') - } - } - mut enc_name := js_enc_name(field_type) - if g.table.get_type_symbol(field.typ).kind == .enum_ { - enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}));') - } else { - enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));') - } - } + g.gen_struct_enc_dec(sym.info, styp, mut enc, mut dec) } // cJSON_delete // p.cgen.fns << '$dec return opt_ok(res); \n}' @@ -138,6 +110,73 @@ $enc_fn_dec { g.gowrappers.writeln(enc.str()) } +[inline] +fn (mut g Gen) gen_struct_enc_dec(type_info table.TypeInfo, styp string, mut enc strings.Builder, mut dec strings.Builder) { + info := type_info as table.Struct + for field in info.fields { + if field.attrs.contains('skip') { + continue + } + mut name := field.name + for attr in field.attrs { + if attr.name == 'json' { + name = attr.arg + break + } + } + field_type := g.typ(field.typ) + field_sym := g.table.get_type_symbol(field.typ) + if field.attrs.contains('raw') { + dec.writeln('\tres.${c_name(field.name)} = tos2(cJSON_PrintUnformatted(' + 'js_get(root, "$name")));') + } else { + // Now generate decoders for all field types in this struct + // need to do it here so that these functions are generated first + g.gen_json_for_type(field.typ) + dec_name := js_dec_name(field_type) + if is_js_prim(field_type) { + dec.writeln('\tres.${c_name(field.name)} = $dec_name (js_get(root, "$name"));') + } else if field_sym.kind == .enum_ { + dec.writeln('\tres.${c_name(field.name)} = json__decode_u64(js_get(root, "$name"));') + } else if field_sym.kind == .alias { + alias := field_sym.info as table.Alias + parent_type := g.typ(alias.parent_type) + parent_dec_name := js_dec_name(parent_type) + if is_js_prim(parent_type) { + dec.writeln('\tres.${c_name(field.name)} = $parent_dec_name (js_get(root, "$name"));') + } else { + g.gen_json_for_type(field.typ) + tmp := g.new_tmp_var() + dec.writeln('\tOption_$field_type $tmp = $dec_name (js_get(root,"$name"));') + dec.writeln('\tif(!${tmp}.ok) {') + dec.writeln('\t\treturn *(Option_$styp*) &$tmp;') + dec.writeln('\t}') + dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + } + } else { + // dec.writeln(' $dec_name (js_get(root, "$name"), & (res . $field.name));') + tmp := g.new_tmp_var() + dec.writeln('\tOption_$field_type $tmp = $dec_name (js_get(root,"$name"));') + dec.writeln('\tif(!${tmp}.ok) {') + dec.writeln('\t\treturn *(Option_$styp*) &$tmp;') + dec.writeln('\t}') + dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + } + } + mut enc_name := js_enc_name(field_type) + if !is_js_prim(field_type) { + if field_sym.kind == .alias { + ainfo := field_sym.info as table.Alias + enc_name = js_enc_name(g.typ(ainfo.parent_type)) + } + } + if field_sym.kind == .enum_ { + enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}));') + } else { + enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));') + } + } +} + fn js_enc_name(typ string) string { suffix := if typ.ends_with('*') { typ.replace('*', '') } else { typ } name := 'json__encode_$suffix'