0

I'm trying to use Wikidata's Lua models.

I need to search for a specific ID in Wikidata's entities :

    subjectitemofthisproperty = 'Q' .. tostring( entity['claims']['P1629'][1]["mainsnak"]["datavalue"]["value"]["numeric-id"] )

The main problem is that some entities don't have the entity['claims']['P1629'][1]["mainsnak"]["datavalue"]["value"]["numeric-id"] subfield.

So that Lua returns:

Lua error in Module:LoPwS_row at line 80: attempt to index field 'P1629' (a nil value).

If I do a :

if entity['claims']['P1629'][1]["mainsnak"]["datavalue"]["value"]["numeric-id"] ~= nil then

It will not work because the condition call the field and, then, return the same error.

Is there a simple solution for test if a field exists? Thanks!

itsmysterybox
  • 2,748
  • 3
  • 21
  • 26
Xiiryo
  • 3,021
  • 5
  • 31
  • 48

2 Answers2

1

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.

1

You can write a simple function which returns nil if there is nil somewhere in the table chain. Let's call it lookup:

function lookup(t, ...)
    for _, k in ipairs{...} do
        t = t[k]
        if not t then
            return nil
        end
    end
    return t
end

-- Test it
t = {a = {b = {c = 5}}}
lookup(t, 'a', 'x', 'b') -- Returns nil
lookup(t, 'a', 'b', 'c') -- Returns 5
Oleg Andriyanov
  • 5,069
  • 1
  • 22
  • 36
  • Thanks. The content of entity['claims'] has to be entity['claims']['P1629'] or nil for it to work isn't it ? – Xiiryo Nov 03 '18 at 21:02
  • @Thibd I haven't understood your question. All values on the path to the queried value have to be tables (or other indexable objects), or `nil`s. But you can add additional checks on the type of object on each iteration. – Oleg Andriyanov Nov 03 '18 at 21:20
  • Ok, after some test I get it : calling mytable[nonexstingkey] returns nil and not an error as I firstly tough. This is calling mytable[nonexistingkey][nonexistingsubkey] that generate the error. – Xiiryo Nov 04 '18 at 17:34
  • Solved. Thanks a lot. – Xiiryo Nov 04 '18 at 17:36