An efficient and clean way of making nil checks?

This has recently become a problem in my Scripts. I use OOP when writing Module Scripts and so my hierarchy tree can get quite long and requires checking for nils on each and every step.

To provide an example:

-- Tile Entity Class Module Script

function TileEntityClass:GetAmountOfItemInSlot(slotCoordinates: string, item: string): number
	if Slot[slotCoordinates] then
		if Slot[slotCoordinates]["Item"] then
			if Slot[slotCoordinates]["Item"]["Content"][item] then
				return Slot[slotCoordinates]["Item"]["Content"][item]
			end
		end
	end
end

This is difficult to read, long, untidy and contains way too many if statements. I have tried simply using the final statement (i.e., if Slot[slotCoordinates]["Item"]["Content"][item] then) but it throws errors whenever one of the previous steps in the hierarchy is nil.

Thanks in advance!

For this type of things I use this,

if Slot[slotCoordinates] ~= nil and Slot[slotCoordinates]["Item"] ~= nil and Slot[slotCoordinates]["Item"]["Content"][item] ~= nil then
    return Slot[slotCoordinates]["Item"]["Content"][item]
end

I don’t know if this the best way to do it but it works.

1 Like

I suppose that would make the statements more compact, even if they cannot be avoided. Thanks!

1 Like

You could try something like the below:

-- Example Table --
local ExampleTable = { -- Example dictionary. Trying to mimic what yours might look like.
	
	Slot = {
		Slot1 = {
			Item = {
				Content = {
					Cherry = "Red Cherry";
					Apple = "Bad Apple";
				};
			};
		};

		Slot2 = {
			Item = {
				Content = {
					Egg = "Just an Egg";
					Rice = "Some Rice";
				};
			};
		};
	};	
};

-- Below represents your "item" variable.
local MyItemString = "Apple"; -- Example item we're trying to find

-- Function --
function CheckTable(Start, Path) -- Start = Table you want to search through (even if its not a table, it will just return nil if you try to index something in it. E.g if it was a string), Path = A table full of strings that represent the path (in correct order) to the item you want to find
	local CurrentItem = Start;
	for _, String in ipairs(Path) do
		if CurrentItem[String] then -- Check if the item exist in the table
			CurrentItem = CurrentItem[String]; -- Set the item as currentItem
		else
			warn("Could not find the item ["..String.."] in the table ["..tostring(CurrentItem).."]");
			return;
		end;
	end;
	return CurrentItem; -- Return that item
end;

-- Example usage --
local Item = CheckTable(ExampleTable["Slot"], {"Slot1", "Item", "Content", MyItemString}); -- returns bad apple
print(Item);

local Item = CheckTable(ExampleTable["Slot"], {"Slot2", "Item", "Content", MyItemString}); -- returns nil, since it doesn't exist
print(Item);

local Item = CheckTable(ExampleTable["Slot"], {"Slot2", "ItemThatDoesntExist"}); -- returns nil, doesn't exist as well!
print(Item);

-- Your old code in use
local Slot = ExampleTable["Slot"];

if Slot["Slot1"] then -- "Slot1" would represent your "slotCoordinates" variable (as an example).
	if Slot["Slot1"]["Item"] then
		if Slot["Slot1"]["Item"]["Content"][MyItemString] then
			print("We found the item using the old way!!");
			-- return Slot["Slot1"]["Item"]["Content"][MyItemString];
		end
	end
end

-- New way in use
local Item = CheckTable(ExampleTable["Slot"], {"Slot1", "Item", "Content", MyItemString}); -- returns bad apple

if Item then
	print("We found the item using the new way!!");
	-- return Item; 
end;

Hopefully this helps and/or gives you some ideas/insight!

local function GetNested(...) --> Any
	local Arguments = {...}
	local Len = #Arguments
	
	local Temp = Slots
	for Index, Next in ipairs(Arguments) do
		if type(Temp) ~= 'table' then return nil end
		if Index == Len then continue end
		
		Temp = Temp[Next]
	end
	Temp = Temp[Arguments[Len]]
	return Temp
end
local function GetAmountOfItemInSlot(Coordinate, Item) --> Any
	return GetNested(Coordinate, 'Item', 'Content', Item)
end
3 Likes

i am interested in this code; how does it work? can you explain it to me sorry for bump.

You can write that a lot nicer using recursion

local function GetNested(parent, child , ...)
    if child == nil then 
      return parent
    elseif type(parent) == 'table' then 
      return GetNested(parent[child], ...) 
    else
      return nil
    end
end

which would just be

function TileEntityClass:GetAmountOfItemInSlot(slotCoordinates: string, item: string): number
	return GetNested(Slots, Coordinate, 'Item', 'Content', Item)
end
2 Likes

I realized I did something unnecessary in my initial code. This is how it should look like.

local function GetNested(...) --> Any
	local Arguments = {...}

	local Temp = Slots
	for Index, Next in ipairs(Arguments) do
		if type(Temp) ~= 'table' then return nil end

		Temp = Temp[Next]
	end
	return Temp
end

So first we pack the arguments in a table so they can be iterated through.

if type(Temp) ~= 'table' then return nil end

^^ This statement is for the edge case that Temp isn’t a table and prevents the code from erroring since you can only index a table with a table ofc.

@VerdommeManDevAcc Recursion seems kinda unnecessary in this situation imo. Mine seems more simplified but this statement is subjective.

1 Like

By the time you’re checking how many slots are in an item you should have a rough idea of what data will already exist.

You can remove redundant checks if you know what specific data will already exist by the time you’re checking the amount of items in a specific slot.

Alternatively you can just use a pcall if you’re unsure the data will exist. Example below.

function TileEntityClass:GetAmountOfItemInSlot(slotCoordinates: string, item: string): number
    local success, returnData = pcall(function() 
        return Slot[slotCoordinates]["Item"]["Content"][item]
    end)

    -- If successful, return the data, if the data does not exist this will cause an error; meaning no data is returned.
    if success then return returnData end
end

I modified another function I just made that searched if a thing has all children in a list.

The one you want is the hasNested()

function hasChildren(parent, ...)
	return pcall(function(...)
		for _, childName in pairs({...}) do
			_ = parent[childName]
		end
	end, ...)
end

function hasNested(parent,...)
	return pcall(function(...)
		for _, childName in pairs({...}) do
			parent = parent[childName]
		end
		return parent
	end, ...)
end

You can use it as an if:

local good, result  = hasNested(workspace,"Terrain","Bad")
if not good then warn(result) return end
--we now know for sure workspace.Terrain.Bad exists and can continue on our way
1 Like

Also a benefit of using this is that it will work for instantaces and tables

typeof(Instance) == 'Instance'