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.
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.
-- 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
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
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.
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