Util.Js: Improve JSC Value to GLib.Variant conversion

Stop needlessly wrapping object members and array elements in
variant variants.

Don't wrap object values in variants since the code is already using
vardicts for these. Return a variant array if a JS array contains values
of all the same type and don't wrap these in variants, else return
a tuple, which don't need to be wrapped either.
This commit is contained in:
Michael Gratton 2020-08-28 11:20:27 +10:00 committed by Michael James Gratton
parent 6162785d99
commit 89453931bf
2 changed files with 143 additions and 45 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright 2017,2019 Michael James Gratton <mike@vee.net>
* Copyright © 2017-2020 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
@ -25,6 +25,64 @@ namespace Util.JS {
TYPE
}
/** Supported types of JSC values. */
public enum JscType {
/** Specifies an unsupported value type. */
UNKNOWN,
/** Specifies a JavaScript `undefined` value. */
UNDEFINED,
/** Specifies a JavaScript `null` value. */
NULL,
FUNCTION,
STRING,
NUMBER,
BOOLEAN,
ARRAY,
CONSTRUCTOR,
OBJECT;
/**
* Determines the type of a JSC value.
*
* Returns the type of the given value, or {@link UNKNOWN} if
* it could not be determined.
*/
public static JscType to_type(JSC.Value value) {
if (value.is_undefined()) {
return UNDEFINED;
}
if (value.is_null()) {
return NULL;
}
if (value.is_string()) {
return STRING;
}
if (value.is_number()) {
return NUMBER;
}
if (value.is_boolean()) {
return BOOLEAN;
}
if (value.is_array()) {
return ARRAY;
}
if (value.is_object()) {
return OBJECT;
}
if (value.is_function()) {
return FUNCTION;
}
if (value.is_constructor()) {
return CONSTRUCTOR;
}
return UNKNOWN;
}
}
/**
* Returns a JSC Value as a bool.
*
@ -132,60 +190,80 @@ namespace Util.JS {
*
* Simple value objects (string, number, and Boolean values),
* arrays of these, and objects with these types as properties are
* supported. Arrays are converted to arrays of variants, and
* objects to dictionaries containing string keys and variant
* values. Null or undefined values are returned as an empty maybe
* variant type, since it is not possible to determine the actual
* type.
* supported. Arrays containing objects of the same type are
* converted to arrays, otherwise they are converted to tuples,
* empty arrays are converted to the unit tuple, and objects are
* converted to vardict containing property names as keys and
* values. Null and undefined values are returned as an empty
* maybe variant type, since it is not possible to determine the
* actual type.
*
* Throws a type error if the given value's type is not supported.
*/
public inline GLib.Variant value_to_variant(JSC.Value value)
throws Error {
if (value.is_null() || value.is_undefined()) {
return new GLib.Variant.maybe(GLib.VariantType.VARIANT, null);
}
if (value.is_boolean()) {
return new GLib.Variant.boolean(value.to_boolean());
}
if (value.is_number()) {
return new GLib.Variant.double(value.to_double());
}
if (value.is_string()) {
return new GLib.Variant.string(value.to_string());
}
if (value.is_array()) {
GLib.Variant? variant = null;
switch (JscType.to_type(value)) {
case UNDEFINED:
case NULL:
variant = new GLib.Variant.maybe(GLib.VariantType.VARIANT, null);
break;
case STRING:
variant = new GLib.Variant.string(value.to_string());
break;
case NUMBER:
variant = new GLib.Variant.double(value.to_double());
break;
case BOOLEAN:
variant = new GLib.Variant.boolean(value.to_boolean());
break;
case ARRAY:
int len = to_int32(value.object_get_property("length"));
GLib.Variant[] values = new GLib.Variant[len];
for (int i = 0; i < len; i++) {
values[i] = new GLib.Variant.variant(
value_to_variant(value.object_get_property_at_index(i))
);
if (len == 0) {
variant = new GLib.Variant.tuple({});
} else {
JSC.Value element = value.object_get_property_at_index(0);
var first_type = JscType.to_type(element);
var all_same_type = true;
var values = new GLib.Variant[len];
values[0] = value_to_variant(element);
for (int i = 1; i < len; i++) {
element = value.object_get_property_at_index(i);
values[i] = value_to_variant(element);
all_same_type &= (first_type == JscType.to_type(element));
}
if (!all_same_type) {
variant = new GLib.Variant.tuple(values);
} else {
variant = new GLib.Variant.array(
values[0].get_type(), values
);
}
}
return new GLib.Variant.array(GLib.VariantType.VARIANT, values);
}
if (value.is_object()) {
break;
case OBJECT:
GLib.VariantDict dict = new GLib.VariantDict();
string[] names = value.object_enumerate_properties();
if (names != null) {
foreach (var name in names) {
try {
dict.insert_value(
name,
new GLib.Variant.variant(
value_to_variant(
value.object_get_property(name)
)
)
);
} catch (Error.TYPE err) {
// ignored
}
dict.insert_value(
name,
value_to_variant(value.object_get_property(name))
);
}
}
return dict.end();
variant = dict.end();
break;
default:
throw new Error.TYPE("Unsupported JS type: %s", value.to_string());
}
throw new Error.TYPE("Unsupported JS type: %s", value.to_string());
return variant;
}
/**

View file

@ -1,5 +1,5 @@
/*
* Copyright 2017 Michael Gratton <mike@vee.net>
* Copyright © 2017-2020 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
@ -61,15 +61,35 @@ public class Util.JS.Test : TestCase {
var value = new JSC.Value.array_from_garray(this.context, null);
assert_equal(
value_to_variant(value).print(true),
"@av []"
"()"
);
var array = new GLib.GenericArray<JSC.Value>();
array.add(new JSC.Value.string(this.context, "test"));
value = new JSC.Value.array_from_garray(this.context, array);
assert_equal(
value_to_variant(value).print(true),
"[<'test'>]"
"['test']"
);
array = new GLib.GenericArray<JSC.Value>();
array.add(new JSC.Value.string(this.context, "test1"));
array.add(new JSC.Value.string(this.context, "test2"));
value = new JSC.Value.array_from_garray(this.context, array);
assert_equal(
value_to_variant(value).print(true),
"['test1', 'test2']"
);
array = new GLib.GenericArray<JSC.Value>();
array.add(new JSC.Value.string(this.context, "test"));
array.add(new JSC.Value.boolean(this.context, true));
value = new JSC.Value.array_from_garray(this.context, array);
assert_equal(
value_to_variant(value).print(true),
"('test', true)"
);
value = new JSC.Value.object(this.context, null, null);
assert_equal(
value_to_variant(value).print(true),
@ -80,7 +100,7 @@ public class Util.JS.Test : TestCase {
);
assert_equal(
value_to_variant(value).print(true),
"{'test': <<true>>}"
"{'test': <true>}"
);
}