Reputation: 247
Let's say I have a giant table, something like:
test.test[1].testing.test.test_test
The table isn't guaranteed to exist. Neither are the tables containing it. I would like to just be able to do:
if test.test[1].testing.test.test_test then
print("it exits!")
end
But of course, this would give me an "Attempt to index ? (a nil value)" error if any of the indices aren't yet defined. So many times, I'll end up doing something like this:
if test then
if test.test then
if test.test[1] then
if test.test[1].testing then -- and so on
Is there a better, less-tedious way to accomplish this?
Upvotes: 5
Views: 2265
Reputation: 72402
You can avoid raising errors by setting an __index metamethod for nil:
debug.setmetatable(nil, { __index=function () end })
print(test.test[1].testing.test.test_test)
test = {test = {{testing = {test = {test_test = 5}}}}}
print(test.test[1].testing.test.test_test)
You can also use an empty table:
debug.setmetatable(nil, { __index={} })
Upvotes: 6
Reputation: 11201
Without a helper function, the shortest variant is
if (((((test or {}).test or {})[1] or {}).testing or {}).test or {}).test_test then print"it exists!" end
simply replacing every t[k]
or t.k
with (t or {})[k]
or (t or {}).k
, which will lead to nil
s being replaced with {}
such that subsequent indexing operations succeed (but may again yield nil
).
Caveat: This will very likely create unnecessary garbage tables; it is unlikely that your Lua implementation will "optimize them out". It's also less convenient than writing yourself a helper. The helper I usually write uses a vararg to avoid having to create a temporary table:
function nilget(t, ...)
for i = 1, select("#", ...) do
if t == nil then return nil end
t = t[select(i, ...)]
end
return t
end
Usage: nilget(test, "test", 1, "testing", "test", "test_test")
.
I would recommend not changing the debug metatable of nil
; this effectively relaxes all indexing operations to nil-coalescing indexing operations, which is very likely to hide bugs.
Upvotes: 2
Reputation: 130
Another solution to consider is:
if
test and
test.test and
test.test[1] and
test.test[1].testing and
test.test[1].testing.test and
test.test[1].testing.test.test_test
then
print("it exits!")
end
But of course, I'd try to refactor this as to not need so much nesting.
Upvotes: 0
Reputation: 8523
You can write a function that takes a list of keys to look up and does whatever action you want if it finds the entry. Here's an example:
function forindices(f, table, indices)
local entry = table
for _,idx in ipairs(indices) do
if type(entry) == 'table' and entry[idx] then
entry = entry[idx]
else
entry = nil
break
end
end
if entry then
f()
end
end
test = {test = {{testing = {test = {test_test = 5}}}}}
-- prints "it exists"
forindices(function () print("it exists") end,
test,
{"test", 1, "testing", "test", "test_test"})
-- doesn't print
forindices(function () print("it exists") end,
test,
{"test", 1, "nope", "test", "test_test"})
As an aside, the functional programming concept that solves this kind of problem is the Maybe monad. You could probably solve this with a Lua implementation of monads, though it wouldn't be very nice since there's no syntactic sugar for it.
Upvotes: 3