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/
Добавить комментарий