From 2534946eada21c06dd155c08e8aa44719a914274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi?= <33205215+remimimimi@users.noreply.github.com> Date: Sun, 19 Sep 2021 18:36:26 +0000 Subject: [PATCH] json: support sumtypes (#11549) --- vlib/json/json_test.v | 42 +++++++++++++++++++++++++++++++ vlib/v/gen/c/json.v | 57 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/vlib/json/json_test.v b/vlib/json/json_test.v index 1c9f5b4496..53323ba3d1 100644 --- a/vlib/json/json_test.v +++ b/vlib/json/json_test.v @@ -37,6 +37,48 @@ fn test_decode_top_level_array() { assert x[1].age == 31 } +struct Usr { + name string +} + +struct Item { + tag string +} + +enum Animal { + dog // Will be encoded as `0` + cat +} + +type Entity = Animal | Item | Usr | string | time.Time + +struct SomeGame { + title string + player Entity + other []Entity +} + +fn test_encode_decode_sumtype() ? { + game := SomeGame{ + title: 'Super Mega Game' + player: Usr{'PoopLord69'} + other: [ + Entity(Item{'Pen'}), + Item{'Cookie'}, + Animal.dog, + 'Stool', + time.now(), + ] + } + + enc := json.encode(game) + dec := json.decode(SomeGame, enc) ? + + assert game.title == dec.title + assert game.player == dec.player + assert (game.other[4] as time.Time).unix_time() == (dec.other[4] as time.Time).unix_time() +} + fn bar(payload string) ?Bar { // ?T doesn't work currently result := json.decode(T, payload) ? return result diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index 5a45420517..fb5e4a1d06 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -24,6 +24,7 @@ fn (mut g Gen) gen_json_for_type(typ ast.Type) { mut enc := strings.new_builder(100) sym := g.table.get_type_symbol(utyp) styp := g.typ(utyp) + g.register_optional(utyp) if is_js_prim(sym.name) || sym.kind == .enum_ { return } @@ -39,8 +40,6 @@ fn (mut g Gen) gen_json_for_type(typ ast.Type) { // cJSON_Parse(str) call is added by the compiler // Code gen decoder dec_fn_name := js_dec_name(styp) - // Make sure that this optional type actually exists - g.register_optional(utyp) dec_fn_dec := 'Option_$styp ${dec_fn_name}(cJSON* root)' dec.writeln(' $dec_fn_dec { @@ -91,6 +90,13 @@ $enc_fn_dec { verror('json: $sym.name is not struct') } g.gen_struct_enc_dec(psym.info, styp, mut enc, mut dec) + } else if sym.kind == .sum_type { + enc.writeln('\to = cJSON_CreateObject();') + // Structs. Range through fields + if sym.info !is ast.SumType { + verror('json: $sym.name is not a sumtype') + } + g.gen_sumtype_enc_dec(sym, mut enc, mut dec) } else { enc.writeln('\to = cJSON_CreateObject();') // Structs. Range through fields @@ -109,6 +115,53 @@ $enc_fn_dec { g.gowrappers.writeln(enc.str()) } +[inline] +fn (mut g Gen) gen_sumtype_enc_dec(sym ast.TypeSymbol, mut enc strings.Builder, mut dec strings.Builder) { + info := sym.info as ast.SumType + typ := sym.idx + + for variant in info.variants { + variant_typ := g.typ(variant) + variant_sym := g.table.get_type_symbol(variant) + + g.gen_json_for_type(variant) + g.write_sumtype_casting_fn(variant, typ) + g.definitions.writeln('static inline $sym.cname ${variant_typ}_to_sumtype_${sym.cname}($variant_typ* x);') + + // ENCODING + enc.writeln('\tif (val._typ == $variant) {') + if variant_sym.kind == .enum_ { + enc.writeln('\t\tcJSON_AddItemToObject(o, "$variant_sym.name", json__encode_u64(*val._$variant_typ));') + } else if variant_sym.name == 'time.Time' { + enc.writeln('\t\tcJSON_AddItemToObject(o, "$variant_sym.name", json__encode_i64(val._$variant_typ->_v_unix));') + } else { + enc.writeln('\t\tcJSON_AddItemToObject(o, "$variant_sym.name", json__encode_${variant_typ}(*val._$variant_typ));') + } + enc.writeln('\t}') + + // DECODING + dec.writeln('\tif (strcmp("$variant_sym.name", root->child->string) == 0) {') + tmp := g.new_tmp_var() + if is_js_prim(variant_typ) { + gen_js_get(variant_typ, tmp, variant_sym.name, mut dec, false) + dec.writeln('\t\t$variant_typ value = (${js_dec_name(variant_sym.name)})(jsonroot_$tmp);') + } else if variant_sym.kind == .enum_ { + gen_js_get(variant_typ, tmp, variant_sym.name, mut dec, false) + dec.writeln('\t\t$variant_typ value = json__decode_u64(jsonroot_$tmp);') + } else if variant_sym.name == 'time.Time' { + gen_js_get(variant_typ, tmp, variant_sym.name, mut dec, false) + dec.writeln('\t\t$variant_typ value = time__unix(json__decode_i64(jsonroot_$tmp));') + } else { + gen_js_get_opt(js_dec_name(variant_sym.cname), variant_typ, sym.cname, tmp, + variant_sym.name, mut dec, false) + // dec.writeln('\t\tOption_${variant_typ} $tmp = json__decode_${variant_typ}(js_get(root, "$variant_sym.name"));') + dec.writeln('\t\t$variant_typ value = *($variant_typ*)(${tmp}.data);') + } + dec.writeln('\t\tres = ${variant_typ}_to_sumtype_${sym.cname}(&value);') + dec.writeln('\t}') + } +} + [inline] fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc strings.Builder, mut dec strings.Builder) { info := type_info as ast.Struct