Всем привет. Мало для кого будет секретом, что использование json в ваших проектах на Corona SDK может сделать некоторые вещи довольно удобными. Я обнаружил одну интересную аномалию при работе с библиотекой, которая как показала практика, вовсе не является ошибкой, а скорее особенностью о которой стоит знать и быть к ней готовым. Рассмотрим все подробно.
Допустим у нас есть массив Lua имеющий следующий вид:
Если у вас все нормально с пониманием языка этот массив вам представляется как показано ниже, просто обезжиренный в силу обстоятельства — ТАК МОЖНО:
Если вы решили преобразовать этот массив в json то на выходе вы получите примерно такой результат (конечно же мы не забудем подключить библиотеку json):
Как видите json то же знает о существовании подобного вида упрощенной записи при которой если элементы массива идут последовательно они не будут явно нумероваться, и это замечательно так как не редко json мы применяем в своих проектах для сетевого протокола или сохранений в файл и лишний размер совершенно ни к чему.
Теперь попробуем преобразовать стоку обратно в Lua таблицу и вывести ее содержимое при этом обратим внимание на типы данных у ключей:
Как вы могли заметить результат получился более чем ожидаемым, таблица ar на входе полностью соответствует таблице ar2 на выходе. Теперь выполнив предыдущие 2 пункта, но при этом на входе будет массив в котором есть прерванная последовательность в массиве, допустим мы добавим ключ 6(минуя 5) со значение 'wtf'. Преступаем.
Как видите все опять же получилось замечательно так как json.encode ожидал этот подвох и вместо несуществующего 5 ключа вставил null и по этому все закончилось успехом, мы на этом не остановимся и добавим в массив еще один ключ 777 со значением 1, скорее всего мы ожидаем что таблица преобразуется таким же способом и будет включать в себя 770 null — это конечно не лучший вариант так как места он будет занимать значительно больше, но другого способа в json просто не существует что бы создать полный аналог этой lua таблицы так как в json ключ не может быть явно объявлен как число. Смотрим что получилось.
Как видите энкодер пошел другим путем и преобразовал все ключи с типом значения number в string. Не для кого не секрет что подобная особенность может и будет приводить к ошибкам (если вы об этом не знаете), либо к костылям с применением явного преобразований tonumber/tostring при работе с этой таблицей, в любом случае особого удовольствия это не принесет. Рассмотрим как с этим можно бороться.
Для того что бы решить эту проблему можно написать функцию которая будет преобразовывать в исходное состояние все массивы в которых возникла проблема, далее в коде придется постоянно помнить об этой проблеме и повсеместно применять эту функцию, это нормальный способ, но не лучший и поэтому имеется план Б. Переопределим реализацию функции json.decode так что бы все проблемные места автоматически преобразовывались и это решение бодет работать как на первой вложенности в переданный массив так и на более глубоких вложениях. Переопределение значения функции будем делать сразу после того как подключим к проекту библиотеку json. Имеется следующая реализация:
Теперь попробуем произвести прошлую операцию заново:
Как видите все сработало и массив сохранил былую структуру. На последок хочу добавить что если вам по какой-то причине нужно будет избежать этого конвертирования новый вариант реализации json.decode поддерживает второй необязательный параметр который отключит преобразование.
Всем пока!
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 поддерживает второй необязательный параметр который отключит преобразование.
Всем пока!