📜 ⬆️ ⬇️

JSON Recording

JSON Recording


In one of my programs I needed to write data in JSON format . In short, an XML-like format is quite suitable for replacing Windows INI files or the same XML. It is convenient in that it supports arrays and nesting of its own structures, but at the same time 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. Therefore, initially the following 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}"); 

Koryavno, although quite workable. But the program was actively developed, the data format was changed 5 times a day and the problem of tracking all changes was acute. Despite some formatting of the source code, it was hard not to forget to close any tag or correctly print the necessary number of spaces to format the data file itself. Even in the above fragment, before publication, an error was discovered, a comma was not put between the elements of the array.

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

What did I want? So that in my program I wrote something in 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 moment will substitute the opening and, most importantly, the 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 insertion of JSON entities into each other and the timely closure of tags, the scope of variables is used. Quite simply, an instance of one of the TJson *** classes is created — the key and the opening tag are written and all the following created objects are considered to be its attachments. The copy is destroyed - the closing tag is put.
 #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; 

A simple class, the whole purpose of which is to temporarily associate the generated objects into a kind of tree. For what the overloaded operator () is needed will be clear a bit later.
')
This class has a descendant in which the basic writing functionality in JSON format is laid. The programmer should only override the Write *** function.
 #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++; } } 

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

In my test example, the functions Write *** 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 that there are Objects in the data stream (they look like SIC structures), Arrays (they are also arrays in Africa), and simply “Key: Value” pairs. All this diversity can be mixed and nested in each other, for example in a pair of “Key: Value”. The value can be an Array of Objects. The following classes are created for working with these entities:
 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"); } 

For ease of use of the library in its program defined macros:
 #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 to the first call and false to subsequent calls.

And this is how the script looks in the body of the program, on which the filling of the data file is written:
 void main() { 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 stamped, all parentheses are closed when necessary, in each line the required number of leading spaces - beauty!

Source: https://habr.com/ru/post/230079/


All Articles