How to get the primitive `int` value from a `Variant` in a type-safe way?

Godot Version

v4.2.1.stable.mono.official [b09f793f5]

Question

I’ve been scouring the documentation and search results and surprisingly just cannot find an answer to this:

I have a Variant value. I know it should contain an int value. How do I get that value in a way that doesn’t produce warnings or errors with GDScript’s type system? A type guard like if value is int: [do stuff with value] doesn’t work, I still get an error when trying to use value as an int. A cast like value as int doesn’t work, this produces its own error, nor does int(value).

What’s the correct way to cast or coerce a Variant to a primitive type?

I just tried:

func _ready() -> void:
	var v = 1
	if v == 1 : print("it's 1")
	if int(v) == 1 : print("also")
	if (v as int) == 1 : print("same")

And all seems fine. What kind of errors are you getting?

Here’s an example showing the general form of the code I’m working with and want to make work with types.

I’ve tried any number of things but I cannot seem to get the Variant values from the dictionary to be accepted as typed function arguments without type system errors. For example The argument 1 of the function "do_stuff_with_items_data()" requires the subtype "int" but the supertype "Variant" was provided. as-is, or The value is cast to "int" but has an unknown type. when trying value as int, or The argument 1 of the constructor "int()" requires the subtype "int", "bool", or "float" but the supertype "Variant" was provided. when trying int(value). And similar errors for the strings.

extends Node

enum ItemsEnum {
    ITEM_ONE,
    ITEM_TWO,
}
const items_data: Dictionary = {
    ItemsEnum.ITEM_ONE: {
        "value": 1,
        "title": "Title one",
        "tooltip": "Tooltip one",
    },
    ItemsEnum.ITEM_TWO: {
        "value": 2,
        "title": "Title two",
        "tooltip": "Tooltip two",
    },
}

func _ready():
    for id in items_data:
        self.do_stuff_with_items_data(
            id,
            items_data[id].value,
            items_data[id].title,
            items_data[id].tooltip,
        )

func do_stuff_with_items_data(id: int, value: int, title: String, tooltip: String):
    pass # etc

The solution is to define a Resource instead of this JSON-like data form. Also, you’ll be able to attain disk-saving advantages with Resource, such as the ResourceSaver.save() utility, and directly load it with the load() function.

I am aware that this is an option. But there are two reasons why I would still appreciate an answer to the original question:

  1. This isn’t my code. It involves code from an addon. I could just fully rewrite it, but I would prefer not to.

  2. I very much doubt that this will be the last time I run into a situation, especially trying to work with others’ GDScript code, where I need to know how to work with Variants without just turning off the type errors that are extremely helpful otherwise.

Would v is int helpful for you?

No, it would not be.

I admittedly did not test on 4.2 since I don’t have that version downloaded, but I copied your code into both 4.1 and 4.3beta3, and it worked fine for both of them.

With your project settings configured to emit errors for “Unsafe Cast” and “Unsafe Call Argument”?

Ahh, that explains why i didn’t get them!
Now that I can replicate it, I think you’re out of luck. It seems that those settings treat casting a var to an int (or to anything else I would assume) as unsafe, and since Dictionary cannot be statically typed, you’re stuck with either not using static typing and treating everything as a var, or dealing with seeing/hiding at least one of the errors.
Admittedly I’m not well versed in the deep ins and outs of Godot, so there may be an answer there for you that I don’t know, but I think the general problem is that GDScript relies very heavily on vars and vars aren’t particularly safe to cast.

Can confirm. Get the same warning now that I turned them on.

The one small advance is in the loop:

for id:int in items_data:

It really seems like there should be a way in GDScript to check the type of a Variant’s value and retrieve it. I know this is possible with the C# bindings. Is there really just no way to do it in GDScript, short of turning off type safety?

Bizarrely, I have been able to fix the type errors by casting first to Variant and then to the primitive type. (And using an explicit type with the for loop variable.)

Like so:

extends Node

enum ItemsEnum {
    ITEM_ONE,
    ITEM_TWO,
}
const items_data: Dictionary = {
    ItemsEnum.ITEM_ONE: {
        "value": 1,
        "title": "Title one",
        "tooltip": "Tooltip one",
    },
    ItemsEnum.ITEM_TWO: {
        "value": 2,
        "title": "Title two",
        "tooltip": "Tooltip two",
    },
}

func _ready() -> void:
    for id: int in items_data:
        self.do_stuff_with_items_data(
            id,
            (items_data[id].value as Variant) as int,
            (items_data[id].title as Variant) as String,
            (items_data[id].tooltip as Variant) as String,
        )

func do_stuff_with_items_data(id: int, value: int, title: String, tooltip: String) -> void:
    pass # etc

It’s not helpful but I think the problem comes from casting non-typed variables.
If the variable is actually typed as ‘Variant’ then casting it doesn’t give an error. Something like this:

var test_int: Variant = 1
var test_str1: Variant = "One"
var test_str2: Variant = "Two"

func _ready() -> void:
	for id: int in items_data:
		self.do_stuff_with_items_data(
			id,
			test_int as int,
			test_str1 as String,
			test_str2 as String,
		)

func do_stuff_with_items_data(id: int, value: int, title: String, tooltip: String) -> void:
	pass # etc

Unfortunately we don’t have typed dictionaries :unamused:

I’m confused. I thought that dictionary keys and values were already variants?

But yeah, in any case, having actual typed dictionaries would be very nice.

Actually, nevermind. Declaring the test variables in my code as not-typed works too, no cast warning. But the values from the dictionary are seen as “unknown type”. I don’t know why either…

1 Like
extends Node

enum ItemsEnum {
	ITEM_ONE,
	ITEM_TWO,
}
var items_data: Dictionary = {
	ItemsEnum.ITEM_ONE: {
		"value": 1,
		"title": "Title one",
		"tooltip": "Tooltip one",
	},
	ItemsEnum.ITEM_TWO: {
		"value": 2,
		"title": "Title two",
		"tooltip": "Tooltip two",
	},
}

var Item_ID : ItemsEnum = ItemsEnum.ITEM_ONE

var Current_Value : int :
		get : return items_data[Item_ID].value
		
var Current_Title : String :
		get : return items_data[Item_ID].title

var Current_Tooltip : String :
		get : return items_data[Item_ID].tooltip

func Print_Current_Item() -> void:
	print(Current_Value)
	print(Current_Title)
	print(Current_Tooltip)

func Pass_Current_Item(item_id :ItemsEnum) -> void:
	var Item_ID : ItemsEnum = item_id
	print(Current_Value)
	print(Current_Title)
	print(Current_Tooltip)

@StormWHM This is a weird way to do it, but it did make me think to check if this would work:

func get_item_value(id: int) -> int:
    return items_data[id].value
func get_item_title(id: int) -> String:
    return items_data[id].title

And…it did? Apparently another way to do this besides as Variant as [type] is to return the value from a function?

That definitely looks better. Very compact too.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.