diff --git a/lib/wplua/value.c b/lib/wplua/value.c
index 52f9015fd336cbef693b9c813c322822b55987b2..87a29986f64fc892eb37fc394b8fbf67f510ddd4 100644
--- a/lib/wplua/value.c
+++ b/lib/wplua/value.c
@@ -15,9 +15,10 @@ wplua_table_to_properties (lua_State *L, int idx)
 {
   WpProperties *p = wp_properties_new_empty ();
   const gchar *key, *value;
+  int table = lua_absindex (L, idx);
 
   lua_pushnil(L);
-  while (lua_next (L, idx) != 0) {
+  while (lua_next (L, table) != 0) {
     /* copy key & value to convert them to string */
     lua_pushvalue (L, -2);
     key = lua_tostring (L, -1);
@@ -49,6 +50,104 @@ wplua_properties_to_table (lua_State *L, WpProperties *p)
   }
 }
 
+GVariant *
+wplua_table_to_asv (lua_State *L, int idx)
+{
+  g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
+  int table = lua_absindex (L, idx);
+
+  lua_pushnil (L);
+  while (lua_next (L, table)) {
+    /* each argument must have a string as key */
+    if (lua_type (L, -2) != LUA_TSTRING) {
+      wp_warning ("skipping non-string key");
+      lua_pop (L, 1);
+      continue; /* skip, it's probably harmless */
+    }
+
+    const char *key = lua_tostring (L, -2);
+
+    switch (lua_type (L, -1)) {
+    case LUA_TBOOLEAN:
+      g_variant_builder_add (&b, "{sv}", key,
+          g_variant_new_boolean (lua_toboolean (L, -1)));
+      break;
+
+    case LUA_TNUMBER:
+      if (lua_isinteger (L, -1)) {
+        g_variant_builder_add (&b, "{sv}", key,
+            g_variant_new_int64 (lua_tointeger (L, -1)));
+      } else {
+        g_variant_builder_add (&b, "{sv}", key,
+            g_variant_new_double (lua_tonumber (L, -1)));
+      }
+      break;
+
+    case LUA_TSTRING:
+      g_variant_builder_add (&b, "{sv}", key,
+          g_variant_new_string (lua_tostring (L, -1)));
+      break;
+
+    case LUA_TTABLE:
+      g_variant_builder_add (&b, "{sv}", key, wplua_table_to_asv (L, -1));
+      break;
+
+    default:
+      wp_warning ("skipping bad value (its type cannot be represented in GVariant)");
+      break;
+    }
+
+    lua_pop (L, 1);
+  }
+
+  return g_variant_builder_end (&b);
+}
+
+void
+wplua_asv_to_table (lua_State *L, GVariant *asv)
+{
+  lua_newtable (L);
+  if (asv) {
+    GVariantIter iter;
+    g_variant_iter_init (&iter, asv);
+    const gchar *key;
+    GVariant *value;
+
+    while (g_variant_iter_loop (&iter, "{&sv}", &key, &value)) {
+      lua_pushstring (L, key);
+
+      if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) {
+        lua_pushboolean (L, g_variant_get_boolean (value));
+      }
+      else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT16)) {
+        lua_pushinteger (L, g_variant_get_int16 (value));
+      }
+      else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT32)) {
+        lua_pushinteger (L, g_variant_get_int32 (value));
+      }
+      else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT64)) {
+        lua_pushinteger (L, g_variant_get_int64 (value));
+      }
+      else if (g_variant_is_of_type (value, G_VARIANT_TYPE_DOUBLE)) {
+        lua_pushnumber (L, g_variant_get_double (value));
+      }
+      else if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
+        lua_pushstring (L, g_variant_get_string (value, NULL));
+      }
+      else if (g_variant_is_of_type (value, G_VARIANT_TYPE_VARDICT)) {
+        wplua_asv_to_table (L, value);
+      }
+      else {
+        wp_warning ("skipping bad value (its type cannot be represented in lua)");
+        lua_pop (L, 1);
+        continue;
+      }
+
+      lua_settable (L, -3);
+    }
+  }
+}
+
 void
 wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v)
 {
@@ -121,6 +220,10 @@ wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v)
   case G_TYPE_FLAGS:
     g_value_set_flags (v, lua_tointeger (L, idx));
     break;
+  case G_TYPE_VARIANT:
+    if (lua_istable (L, idx))
+      g_value_set_variant (v, wplua_table_to_asv (L, idx));
+    break;
   default:
     break;
   }
@@ -197,9 +300,16 @@ wplua_gvalue_to_lua (lua_State *L, const GValue *v)
     lua_pushstring (L, pspec->name);
     break;
   }
-  case G_TYPE_VARIANT:
+  case G_TYPE_VARIANT: {
+    GVariant *asv = g_value_get_variant (v);
+    if (g_variant_is_of_type (asv, G_VARIANT_TYPE_VARDICT))
+      wplua_asv_to_table (L, asv);
+    else
+      /* FIXME maybe implement if needed */
+      lua_pushnil (L);
+    break;
+  }
   default:
-    /* FIXME implement */
     lua_pushnil (L);
     break;
   }
diff --git a/lib/wplua/wplua.h b/lib/wplua/wplua.h
index 502932d1940d4423de799504a590f9c4fc115a89..1874a140b710753791ff1ff0d5998f5c517c7397 100644
--- a/lib/wplua/wplua.h
+++ b/lib/wplua/wplua.h
@@ -72,6 +72,9 @@ int wplua_gvalue_to_lua (lua_State *L, const GValue *v);
 WpProperties * wplua_table_to_properties (lua_State *L, int idx);
 void wplua_properties_to_table (lua_State *L, WpProperties *p);
 
+GVariant * wplua_table_to_asv (lua_State *L, int idx);
+void wplua_asv_to_table (lua_State *L, GVariant *p);
+
 gboolean wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
     GError **error);
 gboolean wplua_load_uri (lua_State * L, const gchar *uri, GError **error);
diff --git a/tests/wplua/wplua.c b/tests/wplua/wplua.c
index a36967cd1f32df917d52049d7b9849606b250409..49589a1fe4dce0074efa8be6090d9d06ec8cd1ea 100644
--- a/tests/wplua/wplua.c
+++ b/tests/wplua/wplua.c
@@ -502,6 +502,62 @@ test_wplua_sandbox_config ()
   wplua_free (L);
 }
 
+static void
+test_wplua_convert_asv ()
+{
+  g_autoptr (GError) error = NULL;
+  lua_State *L = wplua_new ();
+
+  g_autoptr (GVariant) v = g_variant_new_parsed ("@a{sv} { "
+      "'test-int': <42>, "
+      "'test-double': <3.14>, "
+      "'test-string': <'foobar'>, "
+      "'test-boolean': <true>, "
+      "'nested-table': <@a{sv} { 'string': <'baz'> }> "
+      "}");
+  wplua_asv_to_table (L, v);
+  lua_setglobal (L, "o");
+
+  const gchar code2[] =
+    "assert (o['test-string'] == 'foobar')\n"
+    "assert (o['test-int'] == 42)\n"
+    "assert (math.abs (o['test-double'] - 3.14) < 0.0000000001)\n"
+    "assert (o['test-boolean'] == true)\n"
+    "assert (o['nested-table']['string'] == 'baz')\n";
+  wplua_load_buffer (L, code2, sizeof (code2) - 1, &error);
+  g_assert_no_error (error);
+
+  lua_getglobal (L, "o");
+  g_autoptr (GVariant) fromlua = wplua_table_to_asv (L, -1);
+
+  gint64 test_int = 0;
+  g_assert_true (g_variant_lookup (fromlua, "test-int", "x", &test_int));
+  g_assert_cmpint (test_int, ==, 42);
+
+  gdouble test_double = 0;
+  g_assert_true (g_variant_lookup (fromlua, "test-double", "d", &test_double));
+  g_assert_cmpfloat_with_epsilon (test_double, 3.14, 0.0000000001);
+
+  const gchar *test_str = NULL;
+  g_assert_true (g_variant_lookup (fromlua, "test-string", "&s", &test_str));
+  g_assert_cmpstr (test_str, ==, "foobar");
+
+  gboolean test_boolean = FALSE;
+  g_assert_true (g_variant_lookup (fromlua, "test-boolean", "b", &test_boolean));
+  g_assert_true (test_boolean);
+
+  g_autoptr (GVariant) nested = NULL;
+  g_assert_true (g_variant_lookup (fromlua, "nested-table", "@a{sv}", &nested));
+  g_assert_nonnull (nested);
+  g_assert_true (g_variant_is_of_type (nested, G_VARIANT_TYPE_VARDICT));
+
+  test_str = NULL;
+  g_assert_true (g_variant_lookup (nested, "string", "&s", &test_str));
+  g_assert_cmpstr (test_str, ==, "baz");
+
+  wplua_free (L);
+}
+
 gint
 main (gint argc, gchar *argv[])
 {
@@ -515,6 +571,7 @@ main (gint argc, gchar *argv[])
   g_test_add_func ("/wplua/signals", test_wplua_signals);
   g_test_add_func ("/wplua/sandbox/script", test_wplua_sandbox_script);
   g_test_add_func ("/wplua/sandbox/config", test_wplua_sandbox_config);
+  g_test_add_func ("/wplua/convert/asv", test_wplua_convert_asv);
 
   return g_test_run ();
 }