[Из песочницы] Запись данных в формате JSON
В одной из моих программ понадобилась запись данных в формате JSON. Вкратце — XML-подобный формат, вполне подходит на замену Windows INI-файлам или тому же XML. Удобен тем, что поддерживает массивы и вложенность собственных структур, но при этом не замусоривает файл данных своими тегами до полной нечитабельности человеком. Вот пример файла данных:
{
«Comment»: «My comment»,
«Count»:10,
«DiskParam»:
{
«DB»:10.000000,
«DBAngle»:1.234000
},
«Range»: true,
«Blades»:
[
{
«Caption»: «A»,
«Value»:65
},
{
«Caption»: «B»,
«Value»:66
},
{
«Caption»: «C»,
«Value»:67
}
],
«Slots»:
[
0,1,2
]
}
Формат довольно простой, вполне можно работать с ним без всяких библиотек. Поэтому первоначально за запись отвечал примерно такой участок кода:
fprintf (pOut,»{\n»);
fprintf (pOut,» \«Comment\»:\»%s\», Header→Comment);
fprintf (pOut,»,\n \«NumSt\»:%d», Header→NumSt);
//Пропущено немного кода
fprintf (pOut,»,\n \«DBMax\»:%lf», Header→DBMax);
fprintf (pOut,»,\n \«Range\»:%s», Header→Range? «true»: «false»);
fprintf (pOut,»,\n \«Blades\»:\n [»);
for (int i=0; i
Key («Ключ1»); Value («Значение1»); Key («Ключ2»); Value («Значение2»); Object («Объект1»); Key («Ключ3»); Value («Значение3»); //Ключ3, Ключ4 являются элементами Объект1 Key («Ключ4»); Value («Значение4»); Array («Массив1»); Key («Ключ5»); Value («Значение5»); //Ключ5…КлючN являются элементами Массив1 Key («Ключ6»); Value («Значение6»); … Key («КлючN»); Value («ЗначениеN»); А компилятор/программа пусть сами учтут отступы, которые определяют структуру файла данных. В нужный момент подставят открывающий и, главное, закрывающий тег. Дело осложнялось тем, что внутри этого скрипта хотелось использовать конструкции C++, например циклы внутри массивов.После нескольких дней непрерывной мозговой осады этой проблемы нашлось довольно изящное решение. Для контроля за вложением друг в друга JSON-сущностей и своевременного закрытия тегов используется область видимости переменных. Все очень просто, создается экземпляр одного из классов TJson*** — записывается ключ и открывающий тег и все следующие созданные объекты считаются его вложениями. Уничтожается экземпляр — ставится закрывающий тег.
#define TCF_USED 1
class TTagCloser { public: TTagCloser *Owner; static TTagCloser *Current; static int Depth; int Flags; int Count; int operator ()(){Flags^=TCF_USED; return Flags&TCF_USED;} TTagCloser (){Count=Flags=0; Owner=Current; Current=this; Depth++;} ~TTagCloser (){Depth--; Current=Owner;} }; TTagCloser *TTagCloser: Current=NULL; int TTagCloser: Depth=-1; Простой класс, все назначение которого — временно связать порожденные объекты в некое подобие дерева. Для чего нужен перегруженный operator () будет понятно чуть позже.У этого класса есть наследник, в котором заложен базовый функционал записи в JSON-формате. Программист должен только переопределить функции Write***.
#define TCF_OBJECT 4 #define TCF_ARRAY 2
class TJsonTagCloser: public TTagCloser { public: void WriteTab (); void WriteInt (int); void WriteDouble (double); void WriteStr (char *);
TJsonTagCloser (char *Key); }; //---------------------------------------------------------------------------- TJsonTagCloser: TJsonTagCloser (char *Key): TTagCloser () { if (Owner) { if (Owner→Count) WriteStr (»,»); if (Owner→Flags&TCF_ARRAY) { if (! Owner→Count) WriteTab (); } else { WriteTab (); WriteStr (»\»); if (Key) WriteStr (Key); WriteStr (»\»:»); } Owner→Count++; } } Функция WriteTab () введена в программу удобства гиков, любящих лазить в файлы данных «Блокнотом». Она должна записать в файл данных перевод строки и число пробелов, соответствующее глубине вложения (TTagCloser: Depth). Если бы форматирование не было нужно, то функция выродилась бы в WriteTab (){;}.У меня в тестовом примере функции Write*** определены так:
#include
void main () { OBJECT («TrashKey») //Ключ корневого объекта игнорируется, даже если ошибочно задан { VALUE («Comment», «My comment»); VALUE («Count», 10); OBJECT («DiskParam») { VALUE («DB», 10.0); VALUE («DBAngle», 1.234); } VALUE («Range», true); ARRAY («Blades») { for (int i='A'; i<'A'+3; i++) OBJECT("TrashKey") //Внутри массива ключ игнорируется, даже если ошибочно задан { VALUE("Caption", (char *)&i); //!!процессорно-зависимый код VALUE("Value", i); } } ARRAY("Slots") for(int i=0; i<3; i++) VALUE("", i); } } Как выглядит сформированный этой программой JSON-файл можно посмотреть в начале статьи. Все запятые проставлены, все скобочки закрыты когда нужно, в каждой строке нужное количество ведущих пробелов — красота!