Using the Variant class

What is Variant?

The Variant<?> class is a wrapper type used on DBus when an arbitrary type can be returned. Like the Object type in Java Variant<?> can be any kind of data, starting from primitive types like int, boolean, double etc. to more complex types like String. To achieve this the Variant class is parameterized and will store the actual data type used inside of the Variant<?>.

One example usage of the Variant<?> type is the DBus Properties interface. In that case a property key is mapped to a Variant<?> which allows mapping the key to any value.

Constraints using Variant

Using Variant<?> allows to wrap an arbitrary type. As good as this sounds it introduces some problems regarding the usage of Variant<?> in Java.

When wrapping simple types, Variant<?> works like expected. A Variant<String> will contain a String, a Variant<Integer> will contain an Integer etc.

The problem appears when wrapping Collections like List or Set or when using arrays. In DBus protocol there are no Collections. The protocol only supports array.

When converting a Variant<List<Integer>> to the DBus protocol it will be translated to a compatible form therefore the fact that a List was used in Java will get lost and the List will become an array.

When getting data from the bus to convert back to Variant<List<Integer>> the information that the Variant should contain a List and not an array is already gone. From DBus standpoint the data is organized as array therefore the Variant will contain an array of int (int[]) and not a List<Integer>.

The same result will be produced when using a Variant<int[]> - but in this case the de-serialized value of int[] is the expected value.

How to put a Collection / Map into a Variant<?>

Putting List<?>, Set<?> or Map<?, ?> into a Variant<?> does not work without a little help because of type erasure of Java. While you know the actual data type of Collections or Maps while writing the code it is not available during runtime. Therefore determining the data type used inside of the Collection/Map passed to the Variant<?> constructor is impossible.

To get around this limitation, you have to use another Variant<?> constructor which expects the “signature” as second argument. The signature is the signature string as defined by the DBus protocol.

Some examples: List<String> -> “as” Set<Integer> -> “ai” Map<String, Boolean> -> “a{sb}”

As nobody can remember all of those protocol details, there is a utility org.freedesktop.dbus.Marshalling.convertJavaClassesToSignature(Class<?>...) method which will convert the given classes to the appropriate DBus signature value.

Sample usage: Marshalling.convertJavaClassesToSignature(List.class, String.class) -> “as” Marshalling.convertJavaClassesToSignature(Set.class, Integer.class) -> “ai” Marshalling.convertJavaClassesToSignature(Map.class, String.class, Boolean.class) -> “a{sb}”

Usage with Variant<?> constructor:

new Variant<>(List.of("foo", "bar"), Marshalling.convertJavaClassesToSignature(List.class, String.class)); new Variant<>(Set.of(1, 2, 3), Marshalling.convertJavaClassesToSignature(Set.class, Integer.class)); new Variant<>(Map.of("foo", true, "bar", false), Marshalling.convertJavaClassesToSignature(Map.class, String.class, Boolean.class));

Changes introduced with dbus-java 5.1.0

Starting with dbus-java 5.1.0 the behavior of Variant<?> has been changed regarding the support of Collections and arrays. Because the correct type cannot be known from a message on de-serialization, it is now always assumed that a List was requested.

This means if a method or property is specified to return a Variant of int array it will be converted to a Variant<List<Integer>>.

This change may conflict with older code but allows a more consistent way to deal with Variant<?> containing Collections.

Therefore de-serialzation will never create a Variant<?> containing any type of array. Arrays will always be represented as List.