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

от автора

Всем привет. Мало для кого будет секретом, что использование 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 поддерживает второй необязательный параметр который отключит преобразование.

Всем пока!


ссылка на оригинал статьи https://habr.com/post/422591/