JSONEncode for table comparing?

I tried to compare tables like this, but it didn’t work:

local table1 = {1}
local table2 = {1}

if table1 == table2 then
    print("1")
end

So I got an idea to use JSONEncode which works, but I’m not sure about a few things.

local httpService = game:GetService("HttpService")
local table1 = {1}
local table2 = {1}

if httpService:JSONEncode(table1) == httpService:JSONEncode(table2) then
    print("1")
end

How does JSONEncode even work? Does it send the table to some website, which converts it into JSON and returns it? Or is it all done in-game? Also, are there any better ways to compare tables, as I don’t know exactly how performant this is?

JSONEncode just transforms the table into a JSON-string format, that’s done locally as far as I’m aware. It’s just often used with http requests, hence it being a method of the service.

Converting it is most likely sub optimal, but you’d really have to try. A different approach can be iterating through one table, checking whether each index holds the same value. If either table has the next index when the other doesn’t, they aren’t the same as well. Only viable with numeric indices where the order is guaranteed the same.

t1 = {1,2,4,5}
t2 = {1,2,4,8}
if #t1 ~= #t2 then
 print("Different length")
 return
end
for i = 1,#t1 do 
 if t2[i] ~= t1[i] then 
  print("different at",i) 
  break 
 end  
 last = i 
end
1 Like

This assumes keys are ordered the same way in the two tables to detect the case of t1 having more keys, but this is not guaranteed.

2 Likes

I knew I was oversimplifying something, thanks. It’s only viable for numeric indices but I didn’t recall in the early morning why key-value pairs could be problematic.

Using JSON encode like this is a perfectly good solution, but you’ll have to pay mind to pitfalls:

  1. It cannot encode tables with both array & dictionary parts (that is, numeric indices & string indices, usually).
  2. Tables with cycles will screw it up.

I wouldn’t try to come up with a more performant solution until you’re certain this comparison is a problem. Because chances are, it’s not.

Sounds a lot like an XY problem to me.

Would you be able to explain in a little more detail why you need to compare two tables?

3 Likes

You could do a deep comparison, but requires doing a deep scan through both tables. And if anything is cyclical then it gets messy. JSON comparison would work assuming keys are kept in same order and data is JSONable. I would avoid the JSON method if possible, because it’s literally assuming that the string representation of both tables is the same.

The deep comparison works by:

  1. Check that all key/values in t1 match key/values in t2
  2. Same as above, but check t2 against t1… OR check t2 doesn’t contain keys not present in t1

(Perhaps there’s an optimization to that logic?)

Here is an implementation:
local compared, ids, nxtID, cacheCmp

local function toID(t)
	if not ids[t] then
		ids[t] = nxtID
		compared[nxtID] = true
		nxtID = nxtID * 2
	end
	return ids[t]
end

local function cmp(src, dst)
	for key, value in next, src do
		local other = dst[key]
		if other ~= value and not (
			type(other) == 'table' and
			type(value) == 'table' and
			cacheCmp(other, value)
		) then
			return false
		end
	end

	return true
end

function cacheCmp(a, b)
	local cmpID = toID(a) + toID(b)
	if compared[cmpID] then
		return true
	end
	compared[cmpID] = true

	return cmp(a, b) and cmp(b, a)
end

return function(a, b)
	compared = {}
	ids = {}
	nxtID = 1
	return cacheCmp(a, b)
end
Here is my test code:
local cmp = require(script.Parent.cmp)

-- recursive table (bad stuff)
local _666 = {}
_666[666] = _666


-- duel-recursive table
local _999 = {}
local _999_2 = {}
_999[999] = _999_2
_999_2[999] = _999

local a = {
	{secret = 'hehe'};
	_666;
	_999;
}

local b = {
	{secret = 'hehe'};
	_666;
	_999;
}

local c = {
	{secret = 'boo'};
	_666;
	_999;
}

print('aa', cmp(a, a)) --> true
print('ab', cmp(a, b)) --> true
print('ac', cmp(a, c)) --> false
print('bc', cmp(b, c)) --> false