tts-community / tts-community-bug-tracker

Community maintained list of Tabletop Simulator bugs.
2 stars 0 forks source link

Result of getData() can not always be used for spawnObjectJSON #32

Closed Sebaestschjin closed 3 years ago

Sebaestschjin commented 3 years ago

Describe the bug When the data ob an object contains an attribute a sparse array, it cannot be used for spawnObjectJSON to spawn a new object (after being encoded with JSON.encode()). Examples of such data are Custom Cards with a DeckID unequal to 1 or objects with states. E.g. they look like this:

Name: ...
CustomDeck:
    3:
        Name: ...

Name: ...
States:
    2:
        Name: ...

When encoding this to JSON it will look like this:

"CustomDeck": [null, null, {"Name": ...}]

"States": [null, {"Name": ...}]

The gaps in the array will be filled with null values. Using the JSON in spawnObjectJSON produces an error: cannot convert a string to a clr type

To Reproduce

  1. Create an object with States
  2. Use getData() on the object and encode using JSON.encode()
  3. call spawnObjectJSON with this encoded JSON
  4. See error

Expected behavior It would expect to be able to use getData() as an input for spawnObjectJSON in all cases (after using JSON.encode()).

Lua Scripts & XML UI:

local data = myObject.getData()
-- alter the data here, otherwise one could simply use `clone()` or `getJSON()`
local json = JSON.encode(data)
spawnObjectJSON(json)

Tabletop Simulator Info (please complete the following information):

Known workarounds Before calling JSON.encode(), the indexes from the sparse arrays can be converted to strings. Known entries are States and CustomDeck.

function fixNumberedArray(data, key)
    if data[key] then
        local numbered = {}
        for i, _ in pairs(data[key]) do
            table.insert(numbered, i)
        end

        for _, v in ipairs(numbered) do
            data[key][tostring(v)] = data[key][v]
            data[key][v] = nil
        end
    end
end

function fixData(data)
    if not state then
        return
    end

    fixNumberedArray(data, 'States')
    fixNumberedArray(data, 'CustomDeck')

    if data.ContainedObjects then
        for _, contained in pairs(data.ContainedObjects) do
            fixData(contained)
        end
    end
end

Additional context When calling getJSON() on such objects the returned JSON already converted the indexes in the sparse array to strings. So it can directly be used for spawnObjectJSON(). So another workaround would be to use that, call JSON.decode(), alter the table and then use JSON.encode(). However this might be extremly slow, depending on the objects' data.

nabbydude commented 3 years ago

the call signature for spawnObjectJSON is spawnObjectJSON({ json = "...", ... }), passing a string (like the json) as the first parameter directly will result in cannot convert a string to a clr type LuaGlobalScriptManager+LuaJsonObjectParameters. This does indeed cause a problem though, but the error message it gives is much more verbose:

Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.Int32,ObjectState]' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'States', line 25, position 13.

Another Workaround: Deserialize explicitly and use spawnObjectData.

spawnObjectData({
  data = JSON.decode(json),
  ...
})
Sebaestschjin commented 3 years ago

Yes, my bad. I forget about the signature, when writing this small example to get the error message. I encountered this, when using the actual signature with the json parameter. I already wondered, that the error did look different than I remembered.

Didn't know there was a spawnObjectData, though. In this case the problem isn't relevant, I guess. When using getData(), there shouldn't be a need to encode/decode into JSON and just use spawnObjectData directly. Then the documentation simply needs an update to add the spawnObjectData (and getData) functions, or am I missing something?

Edit: And there's already a pull request for adding that documentation. :-)