6

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?

4 Answers 4

6

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={} })
Sign up to request clarification or add additional context in comments.

Comments

3

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.

1 Comment

in modern lua you could probably use ... instead of indices
2

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

Comments

0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.