Pull to refresh

Corona SDK аномалия библиотеки json

Reading time 4 min
Views 1.8K
Всем привет. Мало для кого будет секретом, что использование json в ваших проектах на Corona SDK может сделать некоторые вещи довольно удобными. Я обнаружил одну интересную аномалию при работе с библиотекой, которая как показала практика, вовсе не является ошибкой, а скорее особенностью о которой стоит знать и быть к ней готовым. Рассмотрим все подробно.

1. Описание проблемы


Допустим у нас есть массив Lua имеющий следующий вид:

ar = {23,45,56,'weer'}

Если у вас все нормально с пониманием языка этот массив вам представляется как показано ниже, просто обезжиренный в силу обстоятельства — ТАК МОЖНО:

ar = {[1] = 23,[2] = 45,[3] = 56,[4] = 'weer'}

Если вы решили преобразовать этот массив в json то на выходе вы получите примерно такой результат (конечно же мы не забудем подключить библиотеку json):

ar = {[1] = 23,[2] = 45,[3] = 56,[4] = 'weer'}
json = require "json"
local str = json.encode(ar)
print(str)--> [23,45,56,"weer"]

Как видите json то же знает о существовании подобного вида упрощенной записи при которой если элементы массива идут последовательно они не будут явно нумероваться, и это замечательно так как не редко json мы применяем в своих проектах для сетевого протокола или сохранений в файл и лишний размер совершенно ни к чему.

Теперь попробуем преобразовать стоку обратно в Lua таблицу и вывести ее содержимое при этом обратим внимание на типы данных у ключей:

local str = '[23,45,56,"weer"]'
local ar2 = json.decode(str)
for k,v in pairs(ar2)do
	print(k,type(k),v)
end
--[[РЕЗУЛЬТАТ
1	number	23
2	number	45
3	number	56
4	number	weer
]]

Как вы могли заметить результат получился более чем ожидаемым, таблица ar на входе полностью соответствует таблице ar2 на выходе. Теперь выполнив предыдущие 2 пункта, но при этом на входе будет массив в котором есть прерванная последовательность в массиве, допустим мы добавим ключ 6(минуя 5) со значение 'wtf'. Преступаем.

json = require "json"
ar = {23,45,56,'weer',[6] = 'wtf'}
local str = json.encode(ar)
print(str)-->[23,45,56,"weer",null,"wtf"]
local t = json.decode(str)
for k,v in pairs(t)do
	print(k,type(k),v)
end

--[[РЕЗУЛЬТАТ
1	number	23
2	number	45
3	number	56
4	number	weer
6	number	wtf
]]

Как видите все опять же получилось замечательно так как json.encode ожидал этот подвох и вместо несуществующего 5 ключа вставил null и по этому все закончилось успехом, мы на этом не остановимся и добавим в массив еще один ключ 777 со значением 1, скорее всего мы ожидаем что таблица преобразуется таким же способом и будет включать в себя 770 null — это конечно не лучший вариант так как места он будет занимать значительно больше, но другого способа в json просто не существует что бы создать полный аналог этой lua таблицы так как в json ключ не может быть явно объявлен как число. Смотрим что получилось.

json = require "json"
ar = {23,45,56,'weer',[6] = 'wtf',[777] = 1}
local str = json.encode(ar)
print(str)-->{"1":23,"2":45,"3":56,"4":"weer","6":"wtf","777":1}
local t = json.decode(str)
for k,v in pairs(t)do
	print(k,type(k),v)
end

--[[РЕЗУЛЬТАТ
1	string	23
777	string	1
3	string	56
2	string	45
4	string	weer
6	string	wtf
]]

Как видите энкодер пошел другим путем и преобразовал все ключи с типом значения number в string. Не для кого не секрет что подобная особенность может и будет приводить к ошибкам (если вы об этом не знаете), либо к костылям с применением явного преобразований tonumber/tostring при работе с этой таблицей, в любом случае особого удовольствия это не принесет. Рассмотрим как с этим можно бороться.

2.Решение проблемы


Для того что бы решить эту проблему можно написать функцию которая будет преобразовывать в исходное состояние все массивы в которых возникла проблема, далее в коде придется постоянно помнить об этой проблеме и повсеместно применять эту функцию, это нормальный способ, но не лучший и поэтому имеется план Б. Переопределим реализацию функции json.decode так что бы все проблемные места автоматически преобразовывались и это решение бодет работать как на первой вложенности в переданный массив так и на более глубоких вложениях. Переопределение значения функции будем делать сразу после того как подключим к проекту библиотеку json. Имеется следующая реализация:

json = require "json"

local jd = json.decode--сохраняем старую реализацию
local norm_ar
json.decode = function(ar,not_convert)--переопределяем реализацию
	local res = jd(ar)
	--функция устраняет проблему при котором 
	--числовые ключи становятся строчными
	norm_ar = function(root)
		local res = {}
		--копируем таблицу
		for k,v in pairs(root)do
			res[k] = v
		end
		--проходим по таблице
		for k,v in pairs(res) do
			if type(k) == 'string' and--ключ строка
			string.match(tostring(k),'%D') == nil then--строка имеет только цифры
				--преобразуем string -> number
				root[tonumber(k)] = root[k]
				root[k] = nil--удаляем ненужный ключ
			end
			--для вложенных таблиц делаем рекурсивный запуск
			if type(v) == 'table' then
				norm_ar(v)
			end
		end
		return root
	end
	--если есть флаг not_convert не производим конвертирование
	return not_convert and res or norm_ar(res)
end

Теперь попробуем произвести прошлую операцию заново:

ar = {23,45,56,'weer',[6] = 'wtf',[777] = 1}
local str = json.encode(ar)
print(str)-->{"1":23,"2":45,"3":56,"4":"weer","6":"wtf","777":1}
local t = json.decode(str)
for k,v in pairs(t)do
	print(k,type(k),v)
end

--[[РЕЗУЛЬТАТ
1	number	23
2	number	45
3	number	56
4	number	weer
6	number	wtf
777	number	1
--]]

Как видите все сработало и массив сохранил былую структуру. На последок хочу добавить что если вам по какой-то причине нужно будет избежать этого конвертирования новый вариант реализации json.decode поддерживает второй необязательный параметр который отключит преобразование.

Всем пока!
Tags:
Hubs:
+7
Comments 7
Comments Comments 7

Articles