Why do keys have to be strings when transferring data between server and client?

Lets say we have a table containing information on users:

local users = {
	[82347291] = {};
	[82347292] = {};
	[82347293] = {};
}

If you transfer this data between server and client (i.e. via remote events) you will receive the error message: ‘Cannot convert mixed or non-array tables: keys must be strings’

You can normally use this data fine - it only errors when the data is transferred between the server and client.

This is a slight nuisance as I now have to convert the keys to strings when sending the information, then convert back to integers on the receiving end.

How come this happens, and is there a better way to approaching this?

1 Like

I don’t know the exact reason why, but try Encoding and Decoding the table with JSON - that used to work for me.

local HttpService = game:GetService("HttpService")
 
local users = {
	[82347291] = {};
	[82347292] = {};
	[82347293] = {};
}
 
local jsonString= HttpService:JSONEncode(users) -- converts to string
print(jsonString)

local data = HttpService:JSONDecode(jsonString) -- Converts a JSON string back to its original form.

if data.message == "success" then -- This was in the example code from the 'Decode' link above, so I presume there's a chance for JSONDecode to fail?...
 -- do stuff
end
1 Like

Just a side question - it doesn’t seem safe to have user-data accessible by the client, should you not handle this purely on the server, and only provide specific require information to the client when required?

Thanks, I’ll have a look at this.

In the actual system the client requests the data which is then returned by the server, with only relevant information. I just put together this example quickly. Good point though.

1 Like

https://devforum.roblox.com/t/datastore-keys-must-be-strings/24837/2

To summarize the above two posts, it could be for two reasons. First is that the tables are formatted as JSON for transfer, which natively only supports arrays and objects with string keys. The second as @quenty suggests, is that supporting keys other than strings would require refactoring on the C side.

Is there a reason this isn’t possible? No, theoretically with the right transfer formats and internals, this could work.

2 Likes

Since the inception of remote events/functions, yes. :wink:

The problem is as stated above, remotes only accept tables if they have non-mixed indices and are either a dictionary (string keys only) or an array (numerical indices, but must start at 1, and increment without holes, so i.e. 1,2,3,4,5,6,…). The table you’re trying to push through has numerical indices but does not start at 1 and has holes (i.e. 1 between 82347291) and therefore cannot be accepted.

The reason for these constraints is that, presumably, they marshal it to JSON to make sending the table possible, and JSON cannot represent these situations without mangling the keys.


@WingItMan You will find that your code that uses JSONEncode/Decode does not work when you actually run it for the same reason. The error you will get back is “Cannot convert mixed or non-array tables”, in this case it is a non-array table.

4 Likes

Thanks for the clarification. @IdiomicLanguage @buildthomas

Should I stick with converting the keys between strings and integers, or would you recommend another method?

If it’s not important that you can query the values directly, you could store the data as:

local users = {
	{82347291, ...};
	{82347292, ...};
	{82347293, ...};
}

Or;

local users = {
	{Id = 82347291, ...};
	{Id = 82347292, ...};
	{Id = 82347293, ...};
}

Otherwise you could stringify the IDs, or use the player’s name as the key if this is about players that are currently in the server (since the player object can’t change names while it is playing the game). The latter wouldn’t work if this is stored data like i.e. on a leaderboard, of course. Having the player names as keys also has the benefit that your data is more readable if you ever need to print it for debug purposes, and it is easy to index the Player object since you just need to find the child under Players with the same name (rather than looping over Players if you only had the UserId).

5 Likes