Writing data in JSON format

Writing data in JSON format


In one of my programs I needed to write data in JSON format . In short - an XML-like format, it is quite suitable for replacing Windows with INI files or the same XML. It is convenient in that it supports arrays and nesting of its own structures, but it does not litter the data file with its tags until it is completely unreadable by humans. Here is an example data file:
{
  "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
  ]
}

The format is quite simple, it is quite possible to work with it without any libraries. That’s why the initial piece of code was responsible for the record:
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}");

Koryavenko, although it is quite functional. But the program was actively finalized, the data format changed 5 times a day, and there was an acute problem of tracking all changes. Despite some formatting of the source, it was hard not to forget to close some tag or correctly print the required number of spaces to format the data file itself. Even in the above fragment, an error was detected before publication; a comma was not put between the elements of the array.

I decided to slightly mechanize this process and create a micro-library for working with JSON.

What did I want? So that in my program I write something in a pseudo-language:
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");

And let the compiler / program take into account the indents that determine the structure of the data file. At the right time, substitute the opening and, most importantly, closing tag. The matter was complicated by the fact that inside this script I wanted to use C ++ constructs, for example, loops inside arrays.

After several days of continuous brain siege of this problem, a rather elegant solution was found. To control the embedding of JSON entities into each other and the timely closing of tags, the scope of variables is used. Everything is very simple, an instance of one of the TJson *** classes is created - the key and the opening tag are written and all the following objects created are considered to be its attachments. The instance is destroyed - the closing tag is put.
#define TCF_USED   1classTTagCloser
{public:
  TTagCloser *Owner;
  static TTagCloser *Current;
  staticint Depth;
  int Flags;
  int Count;
  intoperator()(){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;

A simple class whose whole purpose is to temporarily link the generated objects into a kind of tree. Why overloaded operator () is needed will be clear a bit later.

This class has an heir in which the basic writing functionality in JSON format is laid. The programmer only needs to override the Write *** functions.
#define TCF_OBJECT 4#define TCF_ARRAY  2classTJsonTagCloser:public TTagCloser
{
public:
  voidWriteTab();
  voidWriteInt(int);
  voidWriteDouble(double);
  voidWriteStr(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++;
  }
}

The WriteTab () function is introduced in the convenience program for geeks who like to climb into Notepad data files. It should write a line break and the number of spaces corresponding to the depth of attachment in the data file (TTagCloser :: Depth). If formatting were not needed, the function would degenerate into WriteTab () {;}.

In my test case, the Write *** functions are defined as follows:
#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-format assumes the presence in the data stream of Objects (looks like SYN structures), Arrays (they are also arrays in Africa) and just “Key: Value” pairs. All this diversity can be mixed and nested, for example, in the “Key: Value” pair. The value can be an Array of Objects. The following classes have been created to work with these entities:
classTJsonArray:public TJsonTagCloser
{
public:
  TJsonArray(char *Key);
  ~TJsonArray();
};
classTJsonObject:public TJsonTagCloser
{
public:
  TJsonObject(char *Key);
  ~TJsonObject();
};
classTJsonValue: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");
}

For the convenience of using the library, macros are defined in your program:
#define ARRAY(k)   for(TJsonArray  array(k);  array();)#define OBJECT(k)  for(TJsonObject object(k); object();)#define VALUE(k,v) {TJsonValue value(k,v);}

So we got to the overloaded operator (). It is needed for a single execution of the body of the for loop, that is, it returns true on the first call, and false on the next call.

And this is how the script looks like in the body of the program, on which the filling of the data file is written:
voidmain(){
  OBJECT("")
  {
    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("")
        {
          VALUE("Caption", (char *)&i);
          VALUE("Value",   i);
        }
    }
    ARRAY("Slots")
      for(int i=0; i<3; i++)
        VALUE("", i);
  }
}

You can see the JSON file generated by this program at the beginning of the article. All commas are affixed, all brackets are closed when needed, in each line the right amount of leading spaces is beauty!

Also popular now: