As a Roblox developer, it is difficult to deal with errors from Datastore related to storing invalid values, such as tables with invalid indices/keys, because the errors don’t give any additional information on why the value is invalid for storage in Datastores.
Examples of how inconclusive the current error messages are:
data:SetAsync("TestKey", {1, 2, 3, game})
> 104: Cannot store Array in DataStore
data:SetAsync("TestKey", {1,{2,3,function() end},4,5})
> 104: Cannot store Array in DataStore
data:SetAsync("TestKey", {a = 1, b = 2, c = game})
> 104: Cannot store Dictionary in DataStore
data:SetAsync("TestKey", {a = 1, b = {c = function() end, d = 3}, e = 4})
> 104: Cannot store Dictionary in DataStore
data:SetAsync("TestKey", {[game] = 1, b =2, c = 3})
> Cannot convert mixed or non-array tables: keys must be strings
data:SetAsync("TestKey", {a = 1, b = {c = 2, [true] = 3}, e = 4})
> Cannot convert mixed or non-array tables: keys must be strings
I would suggest that something along the lines of the following is thrown instead, respective to the six calls above:
Cannot store Array in DataStore: value at input[4] is of invalid type Instance
Cannot store Array in DataStore: value at input[2][3] is of invalid type function
Cannot store Dictionary in DataStore: value at input.c is of invalid type Instance
Cannot store Dictionary in DataStore: value at input.b.c is of invalid type function
Cannot store Dictionary in DataStore: input contains an index of invalid type Instance
Cannot store Dictionary in DataStore: input.b contains an index of invalid type boolean
Note that the exact path to where the issue occurs is given.
I personally use the following code (original version by @Corecii) to generate descriptive warnings/errors when I try to store invalid data. Including here for reference. It will describe the path to the invalid keys/values in the table and exactly the issue that occurred in the respective (sub-)table. If something like this could be internalized into the DataStoreService API, I think it would help out many developers in better understanding what data they can and can’t store, and to track down issues in their code that generated such invalid tables much quicker.
Scan function code
-- checks through the entire passed value/table and gives back a path to invalid key/value
local function scanValidity(tbl, passed, path)
if type(tbl) ~= "table" then
return scanValidity({input = tbl}, {}, {})
end
passed, path = passed or {}, path or {"root"}
passed[tbl] = true
local tblType
do
local key = next(tbl)
if type(key) == "number" then
tblType = "Array"
else
tblType = "Dictionary"
end
end
local last = 0
for key, value in next, tbl do
path[#path + 1] = tostring(key)
if type(key) == "number" then
if tblType == "Dictionary" then
return false, path, "cannot store mixed tables"
elseif key%1 ~= 0 then
return false, path, "cannot store tables with non-integer indices"
elseif key == math.huge or key == -math.huge then
return false, path, "cannot store tables with (-)infinity indices"
end
elseif type(key) ~= "string" then
return false, path, "dictionaries cannot have keys of type " .. typeof(key)
elseif tblType == "Array" then
return false, path, "cannot store mixed tables"
end
if tblType == "Array" then
if last ~= key - 1 then
return false, path, "array has non-sequential indices"
end
last = key
end
if type(value) == "userdata" or type(value) == "function" or type(value) == "thread" then
return false, path, "cannot store value '" .. tostring(value) .. "' of type " .. typeof(value)
end
if type(value) == "table" then
if passed[value] then
return false, path, "cannot store cyclic tables"
end
local isValid, keyPath, reason = scanValidity(value, passed, path)
if not isValid then
return isValid, keyPath, reason
end
end
path[#path] = nil
end
passed[tbl] = nil
return true
end
This example code also checks for issues reported in https://devforum.roblox.com/t/--/176247, regarding mixed tables, arrays not starting at 1 / arrays with holes, and cyclic tables.
If this issue is addressed, it would improve my development experience because I would have to spend less time debugging which parts of my table were invalid and why those parts were invalid. I would just be able to read the error and know where to look for issues in my code.