Запись данных в формате JSON

от автора

Запись данных в формате 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<Header->Count; i++)       {         TElement &e=Element[i];         fprintf(pOut, i?",\n    {":"\n    {");           fprintf(pOut, "\"Caption\":\"%s\"", e.Caption);           fprintf(pOut, ",\"Value\":%lf", e.BaseChar);         fprintf(pOut, "}");       }       fprintf(pOut, "\n  ]");       //Пропущено много кода     fprintf(pOut, "\n}");

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

Решил я этот техпроцесс слегка механизировать и создать микробиблиотеку для работы с JSON.

Что я хотел? Чтобы в своей программе я писал что-то на псевдоязыке:

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 <stdio.h> void TJsonTagCloser::WriteTab(){printf("\n%*s", Depth*2, "");} void TJsonTagCloser::WriteInt(int Value){printf("%d", Value);} void TJsonTagCloser::WriteDouble(double Value){printf("%lf", Value);} void TJsonTagCloser::WriteStr(char *Value){printf("%s", Value);}

JSON-формат предполагает наличие в потоке данных Объектов (смахивают на СИшные структуры), Массивов (они и в Африке массивы) и просто пар «Ключ: Значение». Все это многообразие может быть перемешано и вложено друг в дружку, например в паре «Ключ: Значение» Значением может быть Массив Объектов. Для работы с этими сущностями созданы следующие классы:

class TJsonArray:public TJsonTagCloser { public:   TJsonArray(char *Key);   ~TJsonArray(); }; class TJsonObject:public TJsonTagCloser { public:   TJsonObject(char *Key);   ~TJsonObject(); }; class TJsonValue:public TJsonTagCloser { public:   TJsonValue(char *Key, int    Value):TJsonTagCloser(Key){WriteInt   (Value);}   TJsonValue(char *Key, double Value):TJsonTagCloser(Key){WriteDouble(Value);}   TJsonValue(char *Key, bool   Value):TJsonTagCloser(Key){WriteStr((char *)(Value?"true":"false"));}   TJsonValue(char *Key, char  *Value); }; TJsonArray::TJsonArray(char *Key):TJsonTagCloser(Key) {   Flags|=TCF_ARRAY;    if(Owner && (!(Owner->Flags&TCF_ARRAY) || Owner->Count>1))     WriteTab();    WriteStr("["); } TJsonArray::~TJsonArray() {   WriteTab();   WriteStr("]"); } //---------------------------------------------------------------------------- TJsonObject::TJsonObject(char *Key):TJsonTagCloser(Key) {   Flags|=TCF_OBJECT;   if(Owner && (!(Owner->Flags&TCF_ARRAY) || Owner->Count>1))     WriteTab();   WriteStr("{"); } TJsonObject::~TJsonObject() {   WriteTab();   WriteStr("}"); } TJsonValue::TJsonValue(char *Key, char *Value):TJsonTagCloser(Key) {   if(Value)   {     WriteStr("\"");      WriteStr(Value);      WriteStr("\"");   }   else     WriteStr("null"); }

Для удобства использования библиотеки в своей программе определены макросы:

#define ARRAY(k)   for(TJsonArray  array(k);  array();) #define OBJECT(k)  for(TJsonObject object(k); object();) #define VALUE(k,v) {TJsonValue value(k,v);}

Вот и добрались до перегруженного operator(). Он нужен для однократного выполнения тела цикла for, то есть в первый вызов он возвращает true, а в последующие — false.

А вот так в теле программы выглядит скрипт, на котором пишется заполнение файла данных:

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

ссылка на оригинал статьи http://habrahabr.ru/post/230079/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *