json: add support for aliased struct fields (#6556)
parent
865c30118f
commit
fafe30b6aa
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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,53 +79,26 @@ $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)
|
||||||
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)}));')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// cJSON_delete
|
// cJSON_delete
|
||||||
// p.cgen.fns << '$dec return opt_ok(res); \n}'
|
// p.cgen.fns << '$dec return opt_ok(res); \n}'
|
||||||
|
@ -138,6 +110,73 @@ $enc_fn_dec {
|
||||||
g.gowrappers.writeln(enc.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 {
|
||||||
|
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 {
|
fn js_enc_name(typ string) string {
|
||||||
suffix := if typ.ends_with('*') { typ.replace('*', '') } else { typ }
|
suffix := if typ.ends_with('*') { typ.replace('*', '') } else { typ }
|
||||||
name := 'json__encode_$suffix'
|
name := 'json__encode_$suffix'
|
||||||
|
|
Loading…
Reference in New Issue