json: add support for aliased struct fields (#6556)

pull/7216/head
Xavier B 2020-12-05 10:13:18 -05:00 committed by GitHub
parent 865c30118f
commit fafe30b6aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 110 additions and 57 deletions

View File

@ -171,7 +171,6 @@ struct Data {
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"}},"extra":{"2":{"n1":2,"n2":4,"n3":8,"n4":16},"3":{"n1":3,"n2":9,"n3":27,"n4":81}}}' 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{
@ -195,7 +194,7 @@ fn test_nested_type() {
is_registered: true is_registered: true
typ: 0 typ: 0
pets: 'little foo' pets: 'little foo'
}, }
'Boo': User{ 'Boo': User{
age: 20 age: 20
nums: [5, 3, 1] nums: [5, 3, 1]
@ -204,39 +203,37 @@ fn test_nested_type() {
typ: 4 typ: 4
pets: 'little boo' pets: 'little boo'
} }
}, }
extra: { extra: {
'2': { '2': {
'n1': 2 'n1': 2
'n2': 4 'n2': 4
'n3': 8 'n3': 8
'n4': 16 'n4': 16
}, }
'3': { '3': {
'n1': 3 'n1': 3
'n2': 9 'n2': 9
'n3': 27 'n3': 27
'n4': 81 'n4': 81
}, }
} }
} }
out := json.encode(data) out := json.encode(data)
println(out) println(out)
assert out == data_expected assert out == data_expected
data2 := json.decode(Data, data_expected) or { data2 := json.decode(Data, data_expected) or {
assert false assert false
Data{} Data{}
} }
assert data2.countries.len == data.countries.len 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].name == data.countries[i].name
assert data2.countries[i].cities.len == data.countries[i].cities.len 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 assert data2.countries[i].cities[j].name == data.countries[i].cities[j].name
} }
} }
for key, user in data.users { for key, user in data.users {
assert data2.users[key].age == user.age assert data2.users[key].age == user.age
assert data2.users[key].nums == user.nums 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].typ == user.typ
// assert data2.users[key].pets == user.pets // TODO FIX // assert data2.users[key].pets == user.pets // TODO FIX
} }
for k, v in data.extra { for k, v in data.extra {
for k2, v2 in v { for k2, v2 in v {
assert data2.extra[k][k2] == v2 assert data2.extra[k][k2] == v2
@ -263,11 +259,9 @@ fn test_generic_struct() {
foo_int := Foo<int>{'bar', 12} foo_int := Foo<int>{'bar', 12}
foo_enc := json.encode(foo_int) foo_enc := json.encode(foo_int)
assert foo_enc == '{"name":"bar","data":12}' assert foo_enc == '{"name":"bar","data":12}'
foo_dec := json.decode(Foo<int>, foo_enc) or { foo_dec := json.decode(Foo<int>, foo_enc) or {
exit(1) exit(1)
} }
assert foo_dec.name == 'bar' assert foo_dec.name == 'bar'
assert foo_dec.data == 12 assert foo_dec.data == 12
} }
@ -275,7 +269,6 @@ fn test_generic_struct() {
fn test_errors() { fn test_errors() {
invalid_array := fn () { 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}}}' 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 { json.decode(Data, data) or {
println(err) println(err)
assert err.starts_with('Json element is not an array:') assert err.starts_with('Json element is not an array:')
@ -283,9 +276,8 @@ fn test_errors() {
} }
assert false 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}}}' 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 { json.decode(Data, data) or {
println(err) println(err)
assert err.starts_with('Json element is not an object:') assert err.starts_with('Json element is not an object:')
@ -296,3 +288,25 @@ fn test_errors() {
invalid_array() invalid_array()
invalid_object() 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
}

View File

@ -43,7 +43,6 @@ fn (mut g Gen) gen_json_for_type(typ table.Type) {
g.register_optional(utyp) g.register_optional(utyp)
dec_fn_dec := 'Option_$styp ${dec_fn_name}(cJSON* root)' dec_fn_dec := 'Option_$styp ${dec_fn_name}(cJSON* root)'
dec.writeln(' dec.writeln('
//Option_$styp ${dec_fn_name}(cJSON* root, $styp* res) {
$dec_fn_dec { $dec_fn_dec {
$styp res; $styp res;
if (!root) { if (!root) {
@ -80,13 +79,40 @@ $enc_fn_dec {
g.gen_json_for_type(m.value_type) g.gen_json_for_type(m.value_type)
dec.writeln(g.decode_map(m.key_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)) 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 { } else {
enc.writeln('\to = cJSON_CreateObject();') enc.writeln('\to = cJSON_CreateObject();')
// Structs. Range through fields // Structs. Range through fields
if sym.info !is table.Struct { if sym.info !is table.Struct {
verror('json: $sym.name is not struct') verror('json: $sym.name is not struct')
} }
info := sym.info as table.Struct g.gen_struct_enc_dec(sym.info, styp, mut enc, mut dec)
}
// cJSON_delete
// p.cgen.fns << '$dec return opt_ok(res); \n}'
dec.writeln('\tOption_$styp ret;')
dec.writeln('\topt_ok2(&res, (OptionBase*)&ret, sizeof(res));')
dec.writeln('\treturn ret;\n}')
enc.writeln('\treturn o;\n}')
g.definitions.writeln(dec.str())
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 { for field in info.fields {
if field.attrs.contains('skip') { if field.attrs.contains('skip') {
continue continue
@ -99,6 +125,7 @@ $enc_fn_dec {
} }
} }
field_type := g.typ(field.typ) field_type := g.typ(field.typ)
field_sym := g.table.get_type_symbol(field.typ)
if field.attrs.contains('raw') { if field.attrs.contains('raw') {
dec.writeln('\tres.${c_name(field.name)} = tos2(cJSON_PrintUnformatted(' + 'js_get(root, "$name")));') dec.writeln('\tres.${c_name(field.name)} = tos2(cJSON_PrintUnformatted(' + 'js_get(root, "$name")));')
} else { } else {
@ -108,8 +135,23 @@ $enc_fn_dec {
dec_name := js_dec_name(field_type) dec_name := js_dec_name(field_type)
if is_js_prim(field_type) { if is_js_prim(field_type) {
dec.writeln('\tres.${c_name(field.name)} = $dec_name (js_get(root, "$name"));') dec.writeln('\tres.${c_name(field.name)} = $dec_name (js_get(root, "$name"));')
} else if g.table.get_type_symbol(field.typ).kind == .enum_ { } else if field_sym.kind == .enum_ {
dec.writeln('\tres.${c_name(field.name)} = json__decode_u64(js_get(root, "$name"));') 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 { } else {
// dec.writeln(' $dec_name (js_get(root, "$name"), & (res . $field.name));') // dec.writeln(' $dec_name (js_get(root, "$name"), & (res . $field.name));')
tmp := g.new_tmp_var() tmp := g.new_tmp_var()
@ -121,21 +163,18 @@ $enc_fn_dec {
} }
} }
mut enc_name := js_enc_name(field_type) mut enc_name := js_enc_name(field_type)
if g.table.get_type_symbol(field.typ).kind == .enum_ { 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)}));') enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}));')
} else { } else {
enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));') enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));')
} }
} }
}
// cJSON_delete
// p.cgen.fns << '$dec return opt_ok(res); \n}'
dec.writeln('\tOption_$styp ret;')
dec.writeln('\topt_ok2(&res, (OptionBase*)&ret, sizeof(res));')
dec.writeln('\treturn ret;\n}')
enc.writeln('\treturn o;\n}')
g.definitions.writeln(dec.str())
g.gowrappers.writeln(enc.str())
} }
fn js_enc_name(typ string) string { fn js_enc_name(typ string) string {