Check if a Lua table member exists at any level
Asked Answered
R

6

5

I need to check if a member exists in a table that isn't at the next level, but along a path of members.

foo = {}
if foo.bar.joe then
  print(foo.bar.joe)
end

this will cast an attempt to index field 'bar' (a nil value) because bar isn't defined.

My usual solution is to test the chain, piece-by-piece.

foo = {}
if foo.bar and foo.bar.joe then
  print(foo.bar.joe)
end

but this can be very tedious when there are many nested tables. Are there a better way to do this test than piece-by-piece?

Redouble answered 7/6, 2015 at 1:31 Comment(0)
C
2

I don't understand what you try to mean by "along a path of members". From the example, I assume you are trying to find a value in a "subtable"?

local function search(master, target) --target is a string
    for k,v in next, master do
        if type(v)=="table" and v[target] then return true end
    end
end

A simple example. If you use such a function, you can pass the foo table and the joe string to see if foo.*.joe exists. Hope this helps.

Cicily answered 7/6, 2015 at 4:17 Comment(0)
A
2
debug.setmetatable(nil, {__index = {}})

foo = {}
print(foo.bar.baz.quux)
print(({}).prd.krt.skrz.drn.zprv.zhlt.hrst.zrn)  -- sorry ))
Anode answered 7/6, 2015 at 9:16 Comment(0)
D
2

I think you're looking for something along these lines:

local function get(Obj, Field, ...)
    if Obj == nil or Field == nil then
        return Obj
    else
        return get(Obj[Field], ...)
    end
end

local foo = {x = {y = 7}}
assert(get() == nil)
assert(get(foo) == foo)
assert(get(foo, "x") == foo.x)
assert(get(foo, "x", "y") == 7)
assert(get(foo, "x", "z") == nil)
assert(get(foo, "bar", "joe") == nil)
assert(get(foo, "x", "y") or 41 == 7)
assert(get(foo, "bar", "joe") or 41 == 41)
local Path = {foo, "x", "y"}
assert(get(table.unpack(Path)) == 7)

get simply traverses the given path until a nil is encountered. Seems to do the job. Feel free to think up a better name than "get" though.

As usual, exercise care when combining with or.

I'm impressed by Egor's clever answer, but in general I think we ought to not rely on such hacks.

See also

Dismal answered 7/6, 2015 at 17:33 Comment(1)
I am surprised this simple and elegant answer did not receive any upvote.Cavalier
I
2

To search for an element that is at any level of a table, I would use a method such as this one:

function exists(tab, element)
    local v
    for _, v in pairs(tab) do
        if v == element then
            return true
        elseif type(v) == "table" then
            return exists(v, element)
        end
    end
    return false
end

testTable = {{"Carrot", {"Mushroom", "Lettuce"}, "Mayonnaise"}, "Cinnamon"}
print(exists(testTable, "Mushroom")) -- true
print(exists(testTable, "Apple")) -- false
print(exists(testTable, "Cinnamon")) -- true
Inebriety answered 14/6, 2015 at 6:1 Comment(0)
C
1

If I understood your problem correctly, here's one possibility:

function isField(s)
  local t
  for key in s:gmatch('[^.]+') do
    if t == nil then
      if _ENV[ key ] == nil then return false end
      t = _ENV[ key ]
    else
      if t[ key ] == nil then return false end
      t = t[ key ]
    end
    --print(key) --for DEBUGGING
  end
  return true
end

-- To test

t = {}
t.a = {}
t.a.b = {}
t.a.b.c = 'Found me'

if isField('t.a.b.c') then print(t.a.b.c) else print 'NOT FOUND' end
if isField('t.a.b.c.d') then print(t.a.b.c.d) else print 'NOT FOUND' end

UPDATE: As per cauterite's suggestion, here's a version that also works with locals but has to take two arguments :(

function isField(t,s)
  if t == nil then return false end
  local t = t
  for key in s:gmatch('[^.]+') do
    if t[ key ] == nil then return false end
    t = t[ key ]
  end
  return true
end

-- To test

local
t = {}
t.a = {}
t.a.b = {}
t.a.b.c = 'Found me'

if isField(t,'a.b.c') then print(t.a.b.c) else print 'NOT FOUND' end
if isField(t,'a.b.c.d') then print(t.a.b.c.d) else print 'NOT FOUND' end
Charcoal answered 7/6, 2015 at 20:14 Comment(2)
I'd suggest implementing it as isField(t, s) — trying to obtain the table from the first component in the string only works with globals. For example, local u = {v = 1}; assert(isField('u.v')) fails.Dismal
Updated accordingly. Thanks.Charcoal
S
1

foo = {}

foo.boo = {}

foo.boo.jeo = {}

foo.boo.joe is foo['boo']['joe'] and so

i make next function

function exist(t)

    local words = {}
    local command

    for i,v in string.gmatch(t, '%w+') do words[#words+1] = i end

    command = string.format('a = %s', words[1])

    loadstring(command)()

    if a == nil then return false end

    for count=2, #words do
        a = a[words[count]]
        if a == nil then return false end
    end

    a = nil
    return true
end

foo = {}
foo.boo = {}
foo.boo.joe = {}

print(exist('foo.boo.joe.b.a'))

using loadstring to make temp variable. my lua ver is 5.1

remove loadstring at 5.2 5.3, instead using load

Swor answered 13/6, 2015 at 9:45 Comment(2)
foo.bar.joe is equivalent to _G['foo'].bar.joe (if there's no local variable foo in scope) not _G['foo.bar.joe']! You can't access the latter using the normal variable syntax because it contains characters invalid for variable names.Equidistant
Now it's raising an error (like in the question and for the same reason).Equidistant

© 2022 - 2024 — McMap. All rights reserved.