You can solve this problem using proxy-metatable and questionable Null Object pattern.
Null Object can look like this:
local Null = {}
local NullProto = { __index = function(t,k) return Null end }
setmetatable(Null, NullProto)
Null
will always return itself when you try to index it.
The key idea of the solution is to create a proxy object for the original table, which will use the following logic:
- If some key dosen't exists in the original table then return Null Object
- If some key exists in the original table
- If referenced value is of primitive type then return the value
- If referenced value is of table type then wrap it with proxy and return
The code might look like this
function make_safe_table(nonsafe)
local proto = {
__index = function(t, k)
local val = nonsafe[k]
if val == nil then
return Null
elseif type(val) == 'table' then
return make_safe_table(val)
else
return val
end
end
}
return setmetatable({}, proto)
end
You can use this function like this:
local original = {
nested = {
deep = { hidden = 'value'}
},
simple = 'simple',
[3] = 'third'
}
local safe_original = make_safe_table(original)
print(safe_original.not_exists == Null) -- true
print(safe_original.nested.not_exists == Null) -- true
print(safe_original.nested.deep.not_exists == Null) -- true
print(safe_original.not_exists.still_not_exists == Null) -- true
print(safe_original.nested.deep.hidden) -- 'value'
print(safe_original.simple) -- 'simple'
print(safe_original[3]) -- 'third'
I wouldn't recommend you to use this code in a production environment, because it's not properly tested, but I hope it will help you to build a robust solution.
See https://www.lua.org/pil/13.4.html for more details about metatables.